com.lambdista.util.Try Maven / Gradle / Ivy
/**
* Copyright 2014 Alessandro Lacava
*
* 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 com.lambdista.util;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
*
The {@code Try} type represents a computation that may fail. If the computation is successful returns
* the value wrapped in a {@link Success} otherwise returns the
* {@link java.lang.Exception} wrapped in a {@link Failure}.
*
*
To use {@code Try} you need to call the {@link Try#apply(FailableSupplier)} method passing in a lambda with
* the same signature used for a common {@link java.util.function.Supplier}.
* Indeed {@link FailableSupplier} is just a {@link java.util.function.Supplier} with a
* {@code 'throws Throwable'} added to its {@code 'get'} method.
*
*
For example, {@code Try} can be used to perform division on a user-defined input, without the need to do explicit
* exception-handling in all of the places that an exception might occur.
*
*
An important property of {@code Try} shown in the {@link com.lambdista.example.SumAndDivide#divideWithTry()} method is its ability
* to pipeline (chain if you prefer) operations,
* catching exceptions along the way thanks to its {@link Try#flatMap(java.util.function.Function)} method. If you
* are not a seasoned functional programming geek concepts such as {@code flatMap/map} might not be easy to grasp
* at first. However you'll get used to them and, in the end, you'll love them. Moreover you're going to encounter
* these methods more and more often since some important Java 8 classes already implement them
* (e.g. {@link java.util.Optional} and {@link java.util.stream.Stream}. Anyway for the moment just take for
* granted that to pipeline more than two operations, say N, you just need to chain them by using N - 1
* {@code flatMap} calls and a last call to {@code map}. E.g.: Suppose you have 3 variables (x, y and z) being
* of type {@code Try} and you just wanto to sum them up. The code you need for doing that is the
* following:
*
*
* x.flatMap(a -> y.flatMap(b -> z.map(c -> a + b + c)))
*
*
* Apart from {@code map} and {@code flatMap}, {@code Try} has many other useful methods. See the {@code TryTest}
* class for a thorough coverage of all {@code Try}'s methods.
*
* @param the type returned by the computation
* @author Alessandro Lacava
* @since 2014-06-20
*/
public abstract class Try {
/**
* Ensures that the only possible instances of this class
* are either {@link Success} or {@link Failure}
*/
private Try() {
}
/**
* @return {@code true} if the {@code Try} is a {@code Success}, {@code false} if it's a {@code Failure}
*/
public abstract boolean isSuccess();
/**
* @return {@code true} if the {@code Try} is a {@code Failure}, {@code false} if it's a {@code Success}
*/
public abstract boolean isFailure();
/**
* @return the value wrapped within {@code Success} if it's a {@code Success} or throws
* the exception if {@code this} is a {@code Failure}
* @throws GetOfFailureException if {@code this} is a {@code Failure}
*/
public abstract T get() throws GetOfFailureException;
/**
* @return the value wrapped within {@code Success} if it's a {@code Success} or throws the exception if {@code
* this} is a {@code Failure}
* @throws Throwable if {@code this} is a {@code Failure}
*/
public abstract T checkedGet() throws Throwable;
/**
* Feeds the value to {@link Consumer}'s {@code accept} method if {@code this} is
* a {@link Success}. If {@code this} is a {@link Failure} it takes no action
*
* @param action the {@link Consumer} to use
*/
public abstract void forEach(Consumer super T> action);
/**
* Maps the value of type {@code T} to the value of type {@code U}
* by applying the {@code mapper} function to it if {@code this} is a {@link Success} otherwise it takes no action
* if {@code this} is a {@link Failure}
*
* @param mapper a function to apply to the value of type {@code T}
* @param the type of the result
* @return the result of applying {@code mapper} wrapped in a {@code Try} if it's a {@link Success} or
* {@code this} if it's a {@link Failure}
*/
public abstract Try map(Function super T, ? extends U> mapper);
/**
* Maps the value of type {@code T} to the value of type {@code Try}
* by applying the {@code mapper} function to it if {@code this} is a {@link Success} otherwise it takes no action
* if {@code this} is a {@link Failure}
*
* @param mapper a function to apply to the value which produces a {@code Try}
* of a new value
* @param the type of the result
* @return the result of applying {@code mapper} if it's a {@link Success} or
* {@code this} if it's a {@link Failure}
*/
public abstract Try flatMap(Function super T, ? extends Try> mapper);
/**
* Converts {@code this} to a {@link Failure} if the predicate is not satisfied.
*
* @param predicate the {@link Predicate} to use
* @return a {@code Try} which is a {@link Success} if {@code predicate}
* is satisfied or a {@link Failure} if either {@code this}
* is already a {@link Failure} or the {@code predicate}
* is not satisfied.
*/
public abstract Try filter(Predicate super T> predicate);
/**
* Applies the given function {@code recoverFunc} if {@code this} is a {@link Failure},
* otherwise returns {@code this} if {@code this} is a {@link Success}.
*
* @param recoverFunc the function to apply if {@code this} is a {@link Failure}
* @param the type of the result
* @return a {@code Try} obtained by wrapping the result of applying {@code recoverFunc} to
* the {@link java.lang.Throwable}
*/
public abstract Try recover(Function super Throwable, ? extends U> recoverFunc);
/**
* Applies the given function {@code recoverFunc} if {@code this} is a {@link Failure},
* otherwise returns {@code this} if {@code this} is a {@link Success}.
*
* @param recoverFunc the function to apply if {@code this} is a {@link Failure}
* @param the type of the result
* @return a {@code Try} obtained by applying {@code recoverFunc} to the {@link java.lang.Throwable}
*/
public abstract Try recoverWith(Function super Throwable, ? extends Try> recoverFunc);
/**
* Completes {@code this} {@code Try} with an exception wrapped in a {@link Success}.
*
* @return a {@code Try}, where {@code Throwable} is either the exception that the {@code Try} failed
* with (if {@code this} is a {@link Failure}) or an {@link java.lang.UnsupportedOperationException}
*/
public abstract Try failed();
/**
* Converts this {@code Try} into a {@code java.util.Optional}
*
* @return the result of invoking {@link Optional}'s {@code empty} method if {@code this} is a {@link Failure}
* or {@link Optional}'s {@code of} method if {@code this} is a {@link Success}
*/
public abstract Optional toOptional();
/**
* @param defaultValue the default value to return if {@code this} is a {@link Failure}
* @return the value from {@code this} {@link Success} or the given {@code defaultValue}
* argument if {@code this} is a {@link Failure}
*/
public abstract T getOrElse(T defaultValue);
/**
* @param defaultValue the default value to return if {@code this} is a {@link Failure}
* @return {@code this} {@code Try} if it's a {@link Success} or the given {@code defaultValue}
* argument if {@code this} is a {@link Failure}
*/
public abstract Try orElse(Try defaultValue);
/**
* Completes {@code this} {@code Try} by applying the function {@code failureFunc} to {@code this} if it is of type {@link Failure},
* or the function {@code successFunc} if {@code this} is a {@link Success}.
*
* @param successFunc the function to apply if {@code this} is a {@link Success}
* @param failureFunc the function to apply if {@code this} is a {@link Failure}
* @param the type of the result
* @return a {@code Try} obtained by applying either {@code successFunc} or {@code failureFunc}
*/
public abstract Try transform(Function super T, ? extends Try> successFunc,
Function> failureFunc);
/**
* Converts a {@link Function} expecting an {@link java.lang.AutoCloseable} into a
* {@code Function} which closes the {@code AutoCloseable} after execution.
*
*
* This is equivalent to the try-with-resources statement.
*
* @param consumer {@code Function} expecting an {@code AutoCloseable}
* @return a {@code Function} closing its {@code AutoCloseable} parameter and
* wrapping the outcome as a {@code Try}
* @see #apply(FailableSupplier)
*/
public static Function> apply(Function consumer) {
return closeable -> Try.apply(() -> {
try (T in = closeable) {
return consumer.apply(in);
}
});
}
/**
* Constructs a {@code Try} using the {@link FailableSupplier} parameter. This
* method will ensure any {@link java.lang.Throwable} is caught.
* However note that only {@link java.lang.Exception}s will be wrapped within a {@link Failure}
* object and returned. On the other hand any {@link java.lang.Error} will be rethrown.
*
* @param supplier the {@link FailableSupplier} to use
* @param the type returned by the {@link FailableSupplier}
* @return a {@code Try} object (an instance of either {@link Success} or {@link Failure}
*/
public static Try apply(FailableSupplier supplier) {
try {
return new Success<>(supplier.get());
} catch (Throwable e) {
if (e instanceof Exception) return new Failure<>((Exception) e);
else throw ((Error) e);
}
}
/**
* Converts a {@code Try>} to a {@code Try}.
*
* @param t the {@code Try>} to be flattened.
* @param the type wrapped by {@code Try}
* @return a {@code Try}
*/
public static Try join(Try> t) {
if (t == null) return ((Try) new Failure<>(new NullPointerException("t is null")));
else if (t instanceof Failure>) return ((Try) t);
else return t.get();
}
/**
* Represents the successful result of a computation
*
* @author Alessandro Lacava
* @since 2014-06-20
*/
public static final class Success extends Try {
private final T value;
public Success(T value) {
this.value = value;
}
@Override
public boolean isSuccess() {
return true;
}
@Override
public boolean isFailure() {
return false;
}
@Override
public T get() {
return value;
}
@Override
public T checkedGet() {
return get();
}
@Override
public void forEach(Consumer super T> action) {
action.accept(value);
}
@Override
public Try map(Function super T, ? extends U> mapper) {
return Try.apply(() -> mapper.apply(value));
}
@Override
public Try flatMap(Function super T, ? extends Try> mapper) {
return Try.join(Try.apply(() -> mapper.apply(value)));
}
@Override
public Try filter(Predicate super T> predicate) {
return Try.join(Try.apply(() ->
{
if (predicate.test(value)) {
return this;
} else {
return new Failure<>(new NoSuchElementException("Predicate does not hold for " + value));
}
}
));
}
@SuppressWarnings("unchecked")
@Override
public Try recover(Function super Throwable, ? extends U> recoverFunc) {
return (Try) this;
}
@SuppressWarnings("unchecked")
@Override
public Try recoverWith(Function super Throwable, ? extends Try> recoverFunc) {
return (Try) this;
}
@Override
public Try failed() {
return new Failure<>(new UnsupportedOperationException("Success.failed"));
}
@Override
public Optional toOptional() {
return Optional.ofNullable(value);
}
@Override
public T getOrElse(T defaultValue) {
return value;
}
@Override
public Try orElse(Try defaultValue) {
return this;
}
@Override
public Try transform(Function super T, ? extends Try> successFunc,
Function> failureFunc) {
return Try.join(Try.apply(() -> successFunc.apply(value)));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Success success = (Success) o;
return value.equals(success.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return "Success{" +
"value=" + value +
'}';
}
}
/**
* Represents the failed result of a computation
*
* @author Alessandro Lacava
* @since 2014-06-20
*/
public static final class Failure extends Try {
private final Throwable exception;
private final GetOfFailureException unckeckedException;
public Failure(Throwable exception) {
this.exception = exception;
this.unckeckedException = new GetOfFailureException(exception);
}
@Override
public boolean isSuccess() {
return false;
}
@Override
public boolean isFailure() {
return true;
}
@Override
public T get() {
throw unckeckedException;
}
@Override
public T checkedGet() throws Throwable {
throw exception;
}
@Override
public void forEach(Consumer super T> action) {
}
@SuppressWarnings("unchecked")
@Override
public Try map(Function super T, ? extends U> mapper) {
return (Try) this;
}
@SuppressWarnings("unchecked")
@Override
public Try flatMap(Function super T, ? extends Try> mapper) {
return (Try) this;
}
@Override
public Try filter(Predicate super T> predicate) {
return this;
}
@Override
public Try recover(Function super Throwable, ? extends U> recoverFunc) {
return Try.apply(() -> recoverFunc.apply(exception));
}
@Override
public Try recoverWith(Function super Throwable, ? extends Try> recoverFunc) {
return Try.join(Try.apply(() -> recoverFunc.apply(exception)));
}
@Override
public Try failed() {
return new Success<>(exception);
}
@Override
public Optional toOptional() {
return Optional.empty();
}
@Override
public T getOrElse(T defaultValue) {
return defaultValue;
}
@Override
public Try orElse(Try defaultValue) {
return defaultValue;
}
@Override
public Try transform(Function super T, ? extends Try> successFunc,
Function> failureFunc) {
return Try.join(Try.apply(() -> failureFunc.apply(exception)));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Failure failure = (Failure) o;
return failure.exception.equals(exception);
}
@Override
public int hashCode() {
return exception.hashCode();
}
@Override
public String toString() {
return "Failure{" +
"exception=" + exception +
'}';
}
}
}