javaslang.control.Try Maven / Gradle / Ivy
Show all versions of javaslang Show documentation
/* / \____ _ _ ____ ______ / \ ____ __ _ _____
* / / \/ \ / \/ \ / /\__\/ // \/ \ / / _ \ Javaslang
* _/ / /\ \ \/ / /\ \\__\\ \ // /\ \ /\\/ \__/ / Copyright 2014-now Daniel Dietrich
* /___/\_/ \_/\____/\_/ \_/\__\/__/___\_/ \_// \__/_____/ Licensed under the Apache License, Version 2.0
*/
package javaslang.control;
import javaslang.Value;
import javaslang.collection.Iterator;
import javaslang.collection.List;
import javaslang.collection.Seq;
import java.io.Serializable;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* An implementation similar to Scala's Try control.
*
* @param Value type in the case of success.
* @author Daniel Dietrich
* @since 1.0.0
*/
public interface Try extends Value {
/**
* Creates a Try of a CheckedSupplier.
*
* @param supplier A checked supplier
* @param Component type
* @return {@code Success(supplier.get())} if no exception occurs, otherwise {@code Failure(throwable)} if an
* exception occurs calling {@code supplier.get()}.
*/
static Try of(CheckedSupplier extends T> supplier) {
try {
return new Success<>(supplier.get());
} catch (Throwable t) {
return new Failure<>(t);
}
}
/**
* Creates a Try of a CheckedRunnable.
*
* @param runnable A checked runnable
* @return {@code Success(null)} if no exception occurs, otherwise {@code Failure(throwable)} if an exception occurs
* calling {@code runnable.run()}.
*/
static Try run(CheckedRunnable runnable) {
try {
runnable.run();
return new Success<>(null); // null represents the absence of an value, i.e. Void
} catch (Throwable t) {
return new Failure<>(t);
}
}
/**
* Reduces many {@code Try}s into a single {@code Try} by transforming an
* {@code Iterable>} into a {@code Try>}. If any of
* the {@code Try}s are {@link Try.Failure}, then this returns a {@link Try.Failure}.
*
* @param values An {@link Iterable} of {@code Try}s
* @param type of the Trys
* @return A {@code Try} of a {@link Seq} of results
* @throws NullPointerException if {@code values} is null
*/
static Try> sequence(Iterable extends Try extends T>> values) {
Objects.requireNonNull(values, "values is null");
List list = List.empty();
for (Try extends T> value : values) {
if (value.isFailure()) {
return Try.failure(value.getCause());
}
list = list.prepend(value.get());
}
return Try.success(list.reverse());
}
/**
* Creates a {@link Success} that contains the given {@code value}. Shortcut for {@code new Success<>(value)}.
*
* @param value A value.
* @param Type of the given {@code value}.
* @return A new {@code Success}.
*/
static Try success(T value) {
return new Success<>(value);
}
/**
* Creates a {@link Failure} that contains the given {@code exception}. Shortcut for {@code new Failure<>(exception)}.
*
* @param exception An exception.
* @param Component type of the {@code Try}.
* @return A new {@code Failure}.
*/
static Try failure(Throwable exception) {
return new Failure<>(exception);
}
/**
* Shortcut for {@code andThenTry(consumer::accept)}, see {@link #andThenTry(CheckedConsumer)}.
*
* @param consumer A consumer
* @return this {@code Try} if this is a {@code Failure} or the consumer succeeded, otherwise the
* {@code Failure} of the consumption.
* @throws NullPointerException if {@code consumer} is null
*/
default Try andThen(Consumer super T> consumer) {
Objects.requireNonNull(consumer, "consumer is null");
return andThenTry(consumer::accept);
}
/**
* Passes the result to the given {@code consumer} if this is a {@code Success}.
*
* The main use case is chaining checked functions using method references:
*
*
*
* Try.of(() -> 100)
* .andThen(i -> System.out.println(i));
*
*
*
*
* @param consumer A checked consumer
* @return this {@code Try} if this is a {@code Failure} or the consumer succeeded, otherwise the
* {@code Failure} of the consumption.
* @throws NullPointerException if {@code consumer} is null
*/
default Try andThenTry(CheckedConsumer super T> consumer) {
Objects.requireNonNull(consumer, "consumer is null");
if (isFailure()) {
return this;
} else {
try {
consumer.accept(get());
return this;
} catch (Throwable t) {
return new Failure<>(t);
}
}
}
/**
* Shortcut for {@code andThenTry(runnable::run)}, see {@link #andThenTry(CheckedRunnable)}.
*
* @param runnable A runnable
* @return this {@code Try} if this is a {@code Failure} or the runnable succeeded, otherwise the
* {@code Failure} of the run.
* @throws NullPointerException if {@code runnable} is null
*/
default Try andThen(Runnable runnable) {
Objects.requireNonNull(runnable, "runnable is null");
return andThenTry(runnable::run);
}
/**
* Runs the given runnable if this is a {@code Success}, otherwise returns this {@code Failure}.
*
* The main use case is chaining runnables using method references:
*
*
*
* Try.run(A::methodRef).andThen(B::methodRef).andThen(C::methodRef);
*
*
*
* Please note that these lines are semantically the same:
*
*
*
* Try.run(this::doStuff)
* .andThen(this::doMoreStuff)
* .andThen(this::doEvenMoreStuff);
*
* Try.run(() -> {
* doStuff();
* doMoreStuff();
* doEvenMoreStuff();
* });
*
*
*
* @param runnable A checked runnable
* @return this {@code Try} if this is a {@code Failure} or the runnable succeeded, otherwise the
* {@code Failure} of the run.
* @throws NullPointerException if {@code runnable} is null
*/
default Try andThenTry(CheckedRunnable runnable) {
Objects.requireNonNull(runnable, "runnable is null");
if (isFailure()) {
return this;
} else {
try {
runnable.run();
return this;
} catch (Throwable t) {
return new Failure<>(t);
}
}
}
/**
* Returns {@code Success(throwable)} if this is a {@code Failure(throwable)}, otherwise
* a {@code Failure(new NoSuchElementException("Success.failed()"))} if this is a Success.
*
* @return a new Try
*/
default Try failed() {
if (isFailure()) {
return new Success<>(getCause());
} else {
return new Failure<>(new NoSuchElementException("Success.failed()"));
}
}
/**
* Shortcut for {@code filterTry(predicate::test)}, see {@link #filterTry(CheckedPredicate)}}.
*
* @param predicate A predicate
* @return a {@code Try} instance
* @throws NullPointerException if {@code predicate} is null
*/
default Try filter(Predicate super T> predicate) {
Objects.requireNonNull(predicate, "predicate is null");
return filterTry(predicate::test);
}
/**
* Returns {@code this} if this is a Failure or this is a Success and the value satisfies the predicate.
*
* Returns a new Failure, if this is a Success and the value does not satisfy the Predicate or an exception
* occurs testing the predicate.
*
* @param predicate A checked predicate
* @return a {@code Try} instance
* @throws NullPointerException if {@code predicate} is null
*/
default Try filterTry(CheckedPredicate super T> predicate) {
Objects.requireNonNull(predicate, "predicate is null");
if (isFailure()) {
return this;
} else {
try {
if (predicate.test(get())) {
return this;
} else {
return new Failure<>(new NoSuchElementException("Predicate does not hold for " + get()));
}
} catch (Throwable t) {
return new Failure<>(t);
}
}
}
/**
* Shortcut for {@code flatMapTry(mapper::apply)}, see {@link #flatMapTry(CheckedFunction)}.
*
* @param mapper A mapper
* @param The new component type
* @return a {@code Try}
* @throws NullPointerException if {@code mapper} is null
*/
default Try flatMap(Function super T, ? extends Try extends U>> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
return flatMapTry((CheckedFunction>) mapper::apply);
}
/**
* FlatMaps the value of a Success or returns a Failure.
*
* @param mapper A mapper
* @param The new component type
* @return a {@code Try}
* @throws NullPointerException if {@code mapper} is null
*/
@SuppressWarnings("unchecked")
default Try flatMapTry(CheckedFunction super T, ? extends Try extends U>> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
if (isFailure()) {
return (Failure) this;
} else {
try {
return (Try) mapper.apply(get());
} catch (Throwable t) {
return new Failure<>(t);
}
}
}
/**
* Gets the result of this Try if this is a Success or throws if this is a Failure.
*
* @return The result of this Try.
* @throws NonFatalException if this is a Failure
*/
@Override
T get();
/**
* Gets the cause if this is a Failure or throws if this is a Success.
*
* @return The cause if this is a Failure
* @throws UnsupportedOperationException if this is a Success
*/
Throwable getCause();
/**
* Checks whether this Try has no result, i.e. is a Failure.
*
* @return true if this is a Failure, returns false if this is a Success.
*/
@Override
boolean isEmpty();
/**
* Checks if this is a Failure.
*
* @return true, if this is a Failure, otherwise false, if this is a Success
*/
boolean isFailure();
/**
* A {@code Try} is a single-valued.
*
* @return {@code true}
*/
@Override
default boolean isSingleValued() {
return true;
}
/**
* Checks if this is a Success.
*
* @return true, if this is a Success, otherwise false, if this is a Failure
*/
boolean isSuccess();
@Override
default Iterator iterator() {
return isSuccess() ? Iterator.of(get()) : Iterator.empty();
}
/**
* Shortcut for {@code mapTry(mapper::apply)}, see {@link #mapTry(CheckedFunction)}.
*
* @param The new component type
* @param mapper A checked function
* @return a {@code Try}
* @throws NullPointerException if {@code mapper} is null
*/
@SuppressWarnings("unchecked")
@Override
default Try map(Function super T, ? extends U> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
return mapTry(mapper::apply);
}
/**
* Runs the given checked function if this is a {@code Success},
* passing the result of the current expression to it.
* If this expression is a {@code Failure} then it'll return a new
* {@code Failure} of type R with the original exception.
*
* The main use case is chaining checked functions using method references:
*
*
*
* Try.of(() -> 0)
* .map(x -> 1 / x); // division by zero
*
*
*
* @param The new component type
* @param mapper A checked function
* @return a {@code Try}
* @throws NullPointerException if {@code mapper} is null
*/
@SuppressWarnings("unchecked")
default Try mapTry(CheckedFunction super T, ? extends U> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
if (isFailure()) {
return (Failure) this;
} else {
try {
return new Success<>(mapper.apply(get()));
} catch (Throwable t) {
return new Failure<>(t);
}
}
}
@Override
default Match.MatchValue.Of> match() {
return Match.of(this);
}
/**
* Consumes the throwable if this is a Failure.
*
* @param action An exception consumer
* @return this
* @throws NullPointerException if {@code action} is null
*/
default Try onFailure(Consumer super Throwable> action) {
Objects.requireNonNull(action, "action is null");
if (isFailure()) {
action.accept(getCause());
}
return this;
}
/**
* Consumes the value if this is a Success.
*
* @param action A value consumer
* @return this
* @throws NullPointerException if {@code action} is null
*/
default Try onSuccess(Consumer super T> action) {
Objects.requireNonNull(action, "action is null");
if (isSuccess()) {
action.accept(get());
}
return this;
}
@SuppressWarnings("unchecked")
default Try orElse(Try extends T> other) {
Objects.requireNonNull(other, "other is null");
return isSuccess() ? this : (Try) other;
}
@SuppressWarnings("unchecked")
default Try orElse(Supplier extends Try extends T>> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return isSuccess() ? this : (Try) supplier.get();
}
default T getOrElseGet(Function super Throwable, ? extends T> other) {
Objects.requireNonNull(other, "other is null");
if (isFailure()) {
return other.apply(getCause());
} else {
return get();
}
}
default void orElseRun(Consumer super Throwable> action) {
Objects.requireNonNull(action, "action is null");
if (isFailure()) {
action.accept(getCause());
}
}
default T getOrElseThrow(Function super Throwable, X> exceptionProvider) throws X {
Objects.requireNonNull(exceptionProvider, "exceptionProvider is null");
if (isFailure()) {
throw exceptionProvider.apply(getCause());
} else {
return get();
}
}
/**
* Applies the action to the value of a Success or does nothing in the case of a Failure.
*
* @param action A Consumer
* @return this {@code Try}
* @throws NullPointerException if {@code action} is null
*/
@Override
default Try peek(Consumer super T> action) {
Objects.requireNonNull(action, "action is null");
if (isSuccess()) {
action.accept(get());
}
return this;
}
/**
* Returns {@code this}, if this is a {@code Success}, otherwise tries to recover the exception of the failure with {@code f},
* i.e. calling {@code Try.of(() -> f.apply(throwable))}.
*
* @param f A recovery function taking a Throwable
* @return a {@code Try}
* @throws NullPointerException if {@code f} is null
*/
default Try recover(Function super Throwable, ? extends T> f) {
Objects.requireNonNull(f, "f is null");
if (isFailure()) {
return Try.of(() -> f.apply(getCause()));
} else {
return this;
}
}
/**
* Returns {@code this}, if this is a Success, otherwise tries to recover the exception of the failure with {@code f},
* i.e. calling {@code f.apply(cause.getCause())}. If an error occurs recovering a Failure, then the new Failure is
* returned.
*
* @param f A recovery function taking a Throwable
* @return a {@code Try}
* @throws NullPointerException if {@code f} is null
*/
@SuppressWarnings("unchecked")
default Try recoverWith(Function super Throwable, ? extends Try extends T>> f) {
Objects.requireNonNull(f, "f is null");
if (isFailure()) {
try {
return (Try) f.apply(getCause());
} catch (Throwable t) {
return new Failure<>(t);
}
} else {
return this;
}
}
/**
* Converts this {@code Try} to an {@link Either}.
*
* @return A new {@code Either}
*/
default Either toEither() {
if (isFailure()) {
return Either.left(getCause());
} else {
return Either.right(get());
}
}
/**
* Transforms this {@code Try}.
*
* @param f A transformation
* @param Type of transformation result
* @return An instance of type {@code U}
* @throws NullPointerException if {@code f} is null
*/
default U transform(Function super Try super T>, ? extends U> f) {
Objects.requireNonNull(f, "f is null");
return f.apply(this);
}
@Override
boolean equals(Object o);
@Override
int hashCode();
@Override
String toString();
/**
* A {@linkplain java.util.function.Consumer} which may throw.
*
* @param the type of value supplied to this consumer.
*/
@FunctionalInterface
interface CheckedConsumer {
/**
* Performs side-effects.
*
* @param value a value
* @throws Throwable if an error occurs
*/
void accept(T value) throws Throwable;
}
/**
* A {@linkplain java.util.function.Function} which may throw.
*
* @param the type of the input to the function
* @param the result type of the function
*/
@FunctionalInterface
interface CheckedFunction {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
* @throws Throwable if an error occurs
*/
R apply(T t) throws Throwable;
}
/**
* A {@linkplain java.util.function.Predicate} which may throw.
*
* @param the type of the input to the predicate
*/
@FunctionalInterface
interface CheckedPredicate {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate, otherwise {@code false}
* @throws Throwable if an error occurs
*/
boolean test(T t) throws Throwable;
/**
* Negates this predicate.
*
* @return A new CheckedPredicate.
*/
default CheckedPredicate negate() {
return t -> !test(t);
}
}
/**
* A {@linkplain java.lang.Runnable} which may throw.
*/
@FunctionalInterface
interface CheckedRunnable {
/**
* Performs side-effects.
*
* @throws Throwable if an error occurs
*/
void run() throws Throwable;
}
/**
* A {@linkplain java.util.function.Supplier} which may throw.
*
* @param the type of results supplied by this supplier
*/
@FunctionalInterface
interface CheckedSupplier {
/**
* Gets a result.
*
* @return a result
* @throws Throwable if an error occurs
*/
R get() throws Throwable;
}
/**
* A succeeded Try.
*
* @param component type of this Success
* @author Daniel Dietrich
* @since 1.0.0
*/
final class Success implements Try, Serializable {
private static final long serialVersionUID = 1L;
private final T value;
/**
* Constructs a Success.
*
* @param value The value of this Success.
*/
private Success(T value) {
this.value = value;
}
@Override
public T get() {
return value;
}
@Override
public Throwable getCause() {
throw new UnsupportedOperationException("getCause on Success");
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean isFailure() {
return false;
}
@Override
public boolean isSuccess() {
return true;
}
@Override
public boolean equals(Object obj) {
return (obj == this) || (obj instanceof Success && Objects.equals(value, ((Success>) obj).value));
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@Override
public String stringPrefix() {
return "Success";
}
@Override
public String toString() {
return stringPrefix() + "(" + value + ")";
}
}
/**
* A failed Try.
*
* @param component type of this Failure
* @author Daniel Dietrich
* @since 1.0.0
*/
final class Failure implements Try, Serializable {
private static final long serialVersionUID = 1L;
private final NonFatalException cause;
/**
* Constructs a Failure.
*
* @param exception A cause of type Throwable, may not be null.
* @throws NullPointerException if exception is null
* @throws Error if the given exception if fatal, i.e. non-recoverable
*/
private Failure(Throwable exception) {
Objects.requireNonNull(exception, "exception is null");
cause = NonFatalException.of(exception);
}
// Throws NonFatal instead of Throwable because it is a RuntimeException which does not need to be checked.
@Override
public T get() throws NonFatalException {
throw cause;
}
@Override
public Throwable getCause() {
return cause.getCause();
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public boolean isFailure() {
return true;
}
@Override
public boolean isSuccess() {
return false;
}
@Override
public boolean equals(Object obj) {
return (obj == this) || (obj instanceof Failure && Objects.equals(cause, ((Failure>) obj).cause));
}
@Override
public String stringPrefix() {
return "Failure";
}
@Override
public int hashCode() {
return Objects.hashCode(cause.getCause());
}
@Override
public String toString() {
return stringPrefix() + "(" + cause.getCause() + ")";
}
}
/**
* An unchecked wrapper for Fatal exceptions.
*
* See {@link NonFatalException}.
*/
final class FatalException extends RuntimeException implements Serializable {
private static final long serialVersionUID = 1L;
private FatalException(Throwable exception) {
super(exception);
}
/**
* Two Fatal exceptions are equal, if they have the same stack trace.
*
* @param o An object
* @return true, if o equals this, false otherwise.
*/
@Override
public boolean equals(Object o) {
return (o == this) || (o instanceof FatalException
&& Arrays.deepEquals(getCause().getStackTrace(), ((FatalException) o).getCause().getStackTrace()));
}
@Override
public int hashCode() {
return Objects.hashCode(getCause());
}
@Override
public String toString() {
return "Fatal(" + getCause() + ")";
}
}
/**
* An unchecked wrapper for non-fatal/recoverable exceptions. The underlying exception can
* be accessed via {@link #getCause()}.
*
* The following exceptions are considered to be fatal/non-recoverable:
*
* - {@linkplain InterruptedException}
* - {@linkplain LinkageError}
* - {@linkplain ThreadDeath}
* - {@linkplain VirtualMachineError} (i.e. {@linkplain OutOfMemoryError} or {@linkplain StackOverflowError})
*
*/
final class NonFatalException extends RuntimeException implements Serializable {
private static final long serialVersionUID = 1L;
private NonFatalException(Throwable exception) {
super(exception);
}
/**
* Wraps the given exception in a {@code NonFatal} or throws an {@link Error} if the given exception is fatal.
*
* Note: InterruptedException is not considered to be fatal. It should be handled explicitly but we cannot
* throw it directly because it is not an Error. If we would wrap it in an Error, we couldn't handle it
* directly. Therefore it is not thrown as fatal exception.
*
* @param exception A Throwable
* @return A new {@code NonFatal} if the given exception is recoverable
* @throws Error if the given exception is fatal, i.e. not recoverable
* @throws NullPointerException if exception is null
*/
static NonFatalException of(Throwable exception) {
Objects.requireNonNull(exception, "exception is null");
if (exception instanceof NonFatalException) {
return (NonFatalException) exception;
} else if (exception instanceof FatalException) {
throw (FatalException) exception;
} else {
final boolean isFatal = exception instanceof InterruptedException
|| exception instanceof LinkageError
|| exception instanceof ThreadDeath
|| exception instanceof VirtualMachineError;
if (isFatal) {
throw new FatalException(exception);
} else {
return new NonFatalException(exception);
}
}
}
/**
* Two NonFatal exceptions are equal, if they have the same stack trace.
*
* @param o An object
* @return true, if o equals this, false otherwise.
*/
@Override
public boolean equals(Object o) {
return (o == this) || (o instanceof NonFatalException
&& Arrays.deepEquals(getCause().getStackTrace(), ((NonFatalException) o).getCause().getStackTrace()));
}
@Override
public int hashCode() {
return Objects.hashCode(getCause());
}
@Override
public String toString() {
return "NonFatal(" + getCause() + ")";
}
}
}