org.jooby.funzy.Try Maven / Gradle / Ivy
package org.jooby.funzy;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Functional try and try-with-resources implementation.
*/
public abstract class Try {
/** Try with a value. */
public static abstract class Value extends Try {
/**
* Gets the success result or {@link Throwing#sneakyThrow(Throwable)} the exception.
*
* @return The success result or {@link Throwing#sneakyThrow(Throwable)} the exception.
*/
public abstract V get();
/**
* Get the success value or use the given function on failure.
*
* @param value Default value provider.
* @return Success or default value.
*/
public V orElseGet(Supplier value) {
return isSuccess() ? get() : value.get();
}
/**
* Get the success value or use the given default value on failure.
*
* @param value Default value.
* @return Success or default value.
*/
public V orElse(V value) {
return isSuccess() ? get() : value;
}
/**
* Get the success value or throw an exception created by the exception provider.
*
* @param provider Exception provider.
* @return Success value.
*/
public V orElseThrow(Throwing.Function provider) {
if (isSuccess()) {
return get();
}
throw Throwing.sneakyThrow(provider.apply(getCause().get()));
}
/**
* Always run the given action, works like a finally clause.
*
* @param action Finally action.
* @return This try result.
*/
@Override public Value onComplete(Throwing.Runnable action) {
return (Value) super.onComplete(action);
}
@Override public Value onComplete(final Throwing.Consumer action) {
return (Value) super.onComplete(action);
}
/**
* Always run the given action, works like a finally clause. Exception and value might be null.
* Exception will be null in case of success.
*
* @param action Finally action.
* @return This try result.
*/
public Value onComplete(final Throwing.Consumer2 action) {
try {
V value = isSuccess() ? get() : null;
action.accept(value, getCause().orElse(null));
return this;
} catch (Throwable x) {
return (Value) failure(x);
}
}
/**
* Run the given action if and only if this is a failure.
*
* @param action Failure action/listener.
* @return This try.
*/
@Override public Value onFailure(final Consumer super Throwable> action) {
super.onFailure(action);
return this;
}
/**
* Run the given action if and only if this is a success.
*
* @param action Success listener.
* @return This try.
*/
@Override public Value onSuccess(final Runnable action) {
super.onSuccess(action);
return this;
}
/**
* Run the given action if and only if this is a success.
*
* @param action Success listener.
* @return This try.
*/
public Value onSuccess(final Consumer action) {
if (isSuccess()) {
action.accept(get());
}
return this;
}
/**
* Recover from failure. The recover function will be executed in case of failure.
*
* @param fn Recover function.
* @return This try on success, a new success try from recover or a failure try in case of exception.
*/
public Value recoverWith(Throwing.Function> fn) {
return recoverWith(Throwable.class, fn);
}
/**
* Recover from failure if and only if the exception is a subclass of the given exception filter.
* The recover function will be executed in case of failure.
*
* @param exception Exception filter.
* @param fn Recover function.
* @param Exception type.
* @return This try on success, a new success try from recover or a failure try in case of exception.
*/
public Value recoverWith(Class exception,
Throwing.Function> fn) {
return (Value) getCause()
.filter(exception::isInstance)
.map(x -> {
try {
return fn.apply((X) x);
} catch (Throwable ex) {
return failure(ex);
}
})
.orElse(this);
}
/**
* Recover from failure. The recover function will be executed in case of failure.
*
* @param fn Recover function.
* @return This try on success, a new success try from recover or a failure try in case of exception.
*/
public Value recover(Throwing.Function fn) {
return recover(Throwable.class, fn);
}
/**
* Recover from failure if and only if the exception is a subclass of the given exception filter.
* The recover function will be executed in case of failure.
*
* @param exception Exception filter.
* @param value Recover value.
* @param Exception type.
* @return This try on success, a new success try from recover or a failure try in case of exception.
*/
public Value recover(Class exception, V value) {
return recoverWith(exception, x -> Try.success(value));
}
/**
* Recover from failure if and only if the exception is a subclass of the given exception filter.
* The recover function will be executed in case of failure.
*
* @param exception Exception filter.
* @param fn Recover function.
* @param Exception type.
* @return This try on success, a new success try from recover or a failure try in case of exception.
*/
public Value recover(Class exception, Throwing.Function fn) {
return recoverWith(exception, x -> Try.apply(() -> fn.apply(x)));
}
/**
* Flat map the success value.
*
* @param mapper Mapper.
* @param New type.
* @return A new try value for success or failure.
*/
public Value flatMap(Throwing.Function> mapper) {
if (isFailure()) {
return (Value) this;
}
try {
return mapper.apply(get());
} catch (Throwable x) {
return new Failure<>(x);
}
}
/**
* Map the success value.
*
* @param mapper Mapper.
* @param New type.
* @return A new try value for success or failure.
*/
public Value map(Throwing.Function mapper) {
return flatMap(v -> new Success<>(mapper.apply(v)));
}
/**
* Get an empty optional in case of failure.
*
* @return An empty optional in case of failure.
*/
public Optional toOptional() {
return isFailure() ? Optional.empty() : Optional.ofNullable(get());
}
@Override public Value unwrap(Class extends X> type) {
return (Value) super.unwrap(type);
}
@Override public Value unwrap(final Throwing.Predicate predicate) {
return (Value) super.unwrap(predicate);
}
@Override public Value wrap(final Throwing.Function wrapper) {
return (Value) super.wrap(wrapper);
}
@Override public Value wrap(final Class extends X> predicate,
final Throwing.Function wrapper) {
return (Value) super.wrap(predicate, wrapper);
}
@Override public Value wrap(final Throwing.Predicate predicate,
final Throwing.Function wrapper) {
return (Value) super.wrap(predicate, wrapper);
}
}
private static class Success extends Value {
private final V value;
public Success(V value) {
this.value = value;
}
@Override public V get() {
return value;
}
@Override public Optional getCause() {
return Optional.empty();
}
}
private static class Failure extends Value {
private final Throwable x;
public Failure(Throwable x) {
this.x = x;
}
@Override public V get() {
throw Throwing.sneakyThrow(x);
}
@Override public Optional getCause() {
return Optional.of(x);
}
}
/**
* Try with resource implementation.
*
* @param Resource type.
*/
public static class ResourceHandler {
private static class ProxyCloseable
implements AutoCloseable, Throwing.Supplier {
private final Throwing.Supplier parent;
private final Throwing.Function
mapper;
private P parentResource;
private R resource;
public ProxyCloseable(Throwing.Supplier
parent, Throwing.Function
mapper) {
this.parent = parent;
this.mapper = mapper;
}
@Override public void close() throws Exception {
try {
Optional.ofNullable(resource)
.ifPresent(Throwing.throwingConsumer(AutoCloseable::close));
} finally {
if (parent instanceof ProxyCloseable) {
((ProxyCloseable) parent).close();
} else {
Optional.ofNullable(parentResource)
.ifPresent(Throwing.throwingConsumer(AutoCloseable::close));
}
}
}
@Override public R tryGet() throws Throwable {
if (parent instanceof ProxyCloseable) {
ProxyCloseable proxy = (ProxyCloseable) parent;
if (proxy.resource == null) {
proxy.get();
}
this.parentResource = (P) proxy.resource;
} else {
this.parentResource = parent.get();
}
this.resource = mapper.apply(parentResource);
return (R) this;
}
}
private final Throwing.Supplier r;
private ResourceHandler(Throwing.Supplier r1) {
this.r = r1;
}
/**
* Map the resource to a new closeable resource.
*
* @param fn Mapper.
* @param New resource type.
* @return A new resource handler.
*/
public ResourceHandler map(Throwing.Function fn) {
return new ResourceHandler<>(new ProxyCloseable<>(this.r, fn));
}
/**
* Apply the resource and produces an output.
*
* @param fn Function to apply.
* @param Output type.
* @return A new try result.
*/
public Value apply(Throwing.Function fn) {
return Try.apply(() -> {
try (R r1 = this.r.get()) {
if (r1 instanceof ProxyCloseable) {
return fn.apply((R) ((ProxyCloseable) r1).resource);
}
return fn.apply(r1);
}
});
}
/**
* Run an operation over the resource.
*
* @param fn Function to apply.
* @return A new try result.
*/
public Try run(Throwing.Consumer fn) {
return Try.run(() -> {
try (R r1 = this.r.get()) {
fn.accept(r1);
}
});
}
}
/**
* Try with resource implementation.
*
* @param Resource type.
* @param Resource type.
*/
public static class ResourceHandler2 {
private final Throwing.Supplier r1;
private final Throwing.Supplier r2;
private ResourceHandler2(Throwing.Supplier r1, Throwing.Supplier r2) {
this.r1 = r1;
this.r2 = r2;
}
public Value apply(Throwing.Function2 fn) {
return Try.apply(() -> {
try (R1 r1 = this.r1.get(); R2 r2 = this.r2.get()) {
return fn.apply(r1, r2);
}
});
}
public Try run(Throwing.Consumer2 fn) {
return Try.run(() -> {
try (R1 r1 = this.r1.get(); R2 r2 = this.r2.get()) {
fn.accept(r1, r2);
}
});
}
}
public static class ResourceHandler3 {
private final Throwing.Supplier r1;
private final Throwing.Supplier r2;
private final Throwing.Supplier r3;
private ResourceHandler3(Throwing.Supplier r1, Throwing.Supplier r2,
Throwing.Supplier r3) {
this.r1 = r1;
this.r2 = r2;
this.r3 = r3;
}
public Value apply(Throwing.Function3 fn) {
return Try.apply(() -> {
try (R1 r1 = this.r1.get(); R2 r2 = this.r2.get(); R3 r3 = this.r3.get()) {
return fn.apply(r1, r2, r3);
}
});
}
public Try run(Throwing.Consumer3 fn) {
return Try.run(() -> {
try (R1 r1 = this.r1.get(); R2 r2 = this.r2.get(); R3 r3 = this.r3.get()) {
fn.accept(r1, r2, r3);
}
});
}
}
public static class ResourceHandler4 {
private final Throwing.Supplier r1;
private final Throwing.Supplier r2;
private final Throwing.Supplier r3;
private final Throwing.Supplier r4;
private ResourceHandler4(Throwing.Supplier r1, Throwing.Supplier r2,
Throwing.Supplier r3, Throwing.Supplier r4) {
this.r1 = r1;
this.r2 = r2;
this.r3 = r3;
this.r4 = r4;
}
public Value apply(Throwing.Function4 fn) {
return Try.apply(() -> {
try (R1 r1 = this.r1.get();
R2 r2 = this.r2.get();
R3 r3 = this.r3.get();
R4 r4 = this.r4.get()) {
return fn.apply(r1, r2, r3, r4);
}
});
}
public Try run(Throwing.Consumer4 fn) {
return Try.run(() -> {
try (R1 r1 = this.r1.get();
R2 r2 = this.r2.get();
R3 r3 = this.r3.get();
R4 r4 = this.r4.get()) {
fn.accept(r1, r2, r3, r4);
}
});
}
}
/**
* Functional try-with-resources:
*
* {@code
* InputStream in = ...;
*
* byte[] content = Try.of(in)
* .apply(in -> read(in))
* .get();
*
* }
*
* Jdbc example:
*
* {@code
* Connection connection = ...;
*
* Try.of(connection)
* .map(c -> c.preparedStatement("..."))
* .map(stt -> stt.executeQuery())
* .apply(rs-> {
* return res.getString("column");
* })
* .get();
*
* }
*
* @param r1 Input resource.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler of(R r1) {
return with(() -> r1);
}
/**
* Functional try-with-resources:
*
* {@code
* InputStream in = ...;
* OutputStream out = ...;
*
* Try.of(in, out)
* .run((from, to) -> copy(from, to))
* .onFailure(Throwable::printStacktrace);
*
* }
*
* @param r1 Input resource.
* @param r2 Input resource.
* @param Resource type.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler2 of(
R1 r1, R2 r2) {
return with(() -> r1, () -> r2);
}
/**
* Functional try-with-resources with 3 inputs.
*
* @param r1 Input resource.
* @param r2 Input resource.
* @param r3 Input resource.
* @param Resource type.
* @param Resource type.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler3 of(
R1 r1, R2 r2, R3 r3) {
return with(() -> r1, () -> r2, () -> r3);
}
/**
* Functional try-with-resources with 4 inputs.
*
* @param r1 Input resource.
* @param r2 Input resource.
* @param r3 Input resource.
* @param r4 Input resource.
* @param Resource type.
* @param Resource type.
* @param Resource type.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler4 of(
R1 r1, R2 r2, R3 r3, R4 r4) {
return with(() -> r1, () -> r2, () -> r3, () -> r4);
}
/**
* Functional try-with-resources:
*
* {@code
* byte[] content = Try.with(() -> newInputStream())
* .apply(in -> read(in))
* .get();
*
* }
*
* Jdbc example:
*
* {@code
* Try.with(() -> newConnection())
* .map(c -> c.preparedStatement("..."))
* .map(stt -> stt.executeQuery())
* .apply(rs-> {
* return res.getString("column");
* })
* .get();
*
* }
*
* @param r1 Input resource.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler with(Throwing.Supplier r1) {
return new ResourceHandler<>(r1);
}
/**
* Functional try-with-resources:
*
* {@code
* Try.with(() -> newIn(), () -> newOut())
* .run((from, to) -> copy(from, to))
* .onFailure(Throwable::printStacktrace);
* }
*
* @param r1 Input resource.
* @param r2 Input resource.
* @param Resource type.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler2 with(
Throwing.Supplier r1, Throwing.Supplier r2) {
return new ResourceHandler2<>(r1, r2);
}
/**
* Functional try-with-resources with 3 inputs.
*
* @param r1 Input resource.
* @param r2 Input resource.
* @param r3 Input resource.
* @param Resource type.
* @param Resource type.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler3 with(
Throwing.Supplier r1, Throwing.Supplier r2, Throwing.Supplier r3) {
return new ResourceHandler3<>(r1, r2, r3);
}
/**
* Functional try-with-resources with 4 inputs.
*
* @param r1 Input resource.
* @param r2 Input resource.
* @param r3 Input resource.
* @param r4 Input resource.
* @param Resource type.
* @param Resource type.
* @param Resource type.
* @param Resource type.
* @return A resource handler.
*/
public final static ResourceHandler4 with(
Throwing.Supplier r1, Throwing.Supplier r2, Throwing.Supplier r3,
Throwing.Supplier r4) {
return new ResourceHandler4<>(r1, r2, r3, r4);
}
/**
* Get a new success value.
*
* @param value Value.
* @param Value type.
* @return A new success value.
*/
public final static Value success(V value) {
return new Success<>(value);
}
/**
* Get a new failure value.
*
* @param x Exception.
* @return A new failure value.
*/
public final static Value failure(Throwable x) {
return new Failure<>(x);
}
/**
* Creates a new try from given value provider.
*
* @param fn Value provider.
* @param Value type.
* @return A new success try or failure try in case of exception.
*/
public static Value apply(Throwing.Supplier extends V> fn) {
try {
return new Success<>(fn.get());
} catch (Throwable x) {
return new Failure(x);
}
}
/**
* Creates a new try from given callable.
*
* @param fn Callable.
* @param Value type.
* @return A new success try or failure try in case of exception.
*/
public static Value call(Callable extends V> fn) {
return apply(fn::call);
}
/**
* Creates a side effect try from given runnable. Don't forget to either throw or log the exception
* in case of failure. Unless, of course you don't care about the exception.
*
* Log the exception:
* {@code
* Try.run(() -> ...)
* .onFailure(x -> x.printStacktrace());
* }
*
* Throw the exception:
* {@code
* Try.run(() -> ...)
* .throwException();
* }
*
* @param runnable Runnable.
* @return A void try.
*/
public static Try run(Throwing.Runnable runnable) {
try {
runnable.run();
return new Success<>(null);
} catch (Throwable x) {
return new Failure(x);
}
}
/**
* True in case of failure.
*
* @return True in case of failure.
*/
public boolean isFailure() {
return getCause().isPresent();
}
/**
* True in case of success.
*
* @return True in case of success.
*/
public boolean isSuccess() {
return !isFailure();
}
/**
* Run the given action if and only if this is a failure.
*
* @param action Failure listener.
* @return This try.
*/
public Try onFailure(Consumer super Throwable> action) {
getCause().ifPresent(action);
return this;
}
/**
* Run the given action if and only if this is a success.
*
* @param action Success listener.
* @return This try.
*/
public Try onSuccess(Runnable action) {
if (isSuccess()) {
action.run();
}
return this;
}
/**
* In case of failure unwrap the exception provided by calling {@link Throwable#getCause()}.
* Useful for clean/shorter stackstrace.
*
* Example for {@link java.lang.reflect.InvocationTargetException}:
*
* {@code
* Try.run(() -> {
* Method m = ...;
* m.invoke(...); //might throw InvocationTargetException
* }).unwrap(InvocationTargetException.class)
* .throwException();
* }
*
* @param type Exception filter.
* @param Exception type.
* @return This try for success or a new failure with exception unwrap.
*/
public Try unwrap(Class extends X> type) {
return unwrap(type::isInstance);
}
/**
* In case of failure unwrap the exception provided by calling {@link Throwable#getCause()}.
* Useful for clean/shorter stackstrace.
*
* Example for {@link java.lang.reflect.InvocationTargetException}:
*
* {@code
* Try.run(() -> {
* Method m = ...;
* m.invoke(...); //might throw InvocationTargetException
* }).unwrap(InvocationTargetException.class::isInstance)
* .throwException();
* }
*
* @param predicate Exception filter.
* @return This try for success or a new failure with exception unwrap.
*/
public Try unwrap(Throwing.Predicate predicate) {
try {
return getCause()
.filter(predicate)
.map(Throwable::getCause)
.filter(Objects::nonNull)
.map(x -> (Try) Try.failure(x))
.orElse(this);
} catch (Throwable x) {
return failure(x);
}
}
/**
* In case of failure wrap an exception matching the given predicate to something else.
*
* @param wrapper Exception mapper.
* @return This try for success or a new failure with exception wrapped.
*/
public Try wrap(Throwing.Function wrapper) {
return wrap(Throwable.class, wrapper);
}
/**
* In case of failure wrap an exception matching the given predicate to something else.
*
* @param predicate Exception predicate.
* @param wrapper Exception mapper.
* @param Exception type.
* @return This try for success or a new failure with exception wrapped.
*/
public Try wrap(Class extends X> predicate,
Throwing.Function wrapper) {
return wrap(predicate::isInstance, wrapper);
}
/**
* In case of failure wrap an exception matching the given predicate to something else.
*
* @param predicate Exception predicate.
* @param wrapper Exception mapper.
* @param Exception type.
* @return This try for success or a new failure with exception wrapped.
*/
public Try wrap(Throwing.Predicate predicate,
Throwing.Function wrapper) {
try {
return getCause()
.filter(x -> predicate.test((X) x))
.map(x -> (Try) Try.failure(wrapper.apply((X) x)))
.orElse(this);
} catch (Throwable x) {
return failure(x);
}
}
/**
* Always run the given action, works like a finally clause.
*
* @param action Finally action.
* @return This try result.
*/
public Try onComplete(Throwing.Runnable action) {
try {
action.run();
return this;
} catch (Throwable x) {
return Try.failure(x);
}
}
/**
* Always run the given action, works like a finally clause. Exception will be null in case of success.
*
* @param action Finally action.
* @return This try result.
*/
public Try onComplete(Throwing.Consumer action) {
try {
action.accept(getCause().orElse(null));
return this;
} catch (Throwable x) {
return Try.failure(x);
}
}
/**
* Propagate/throw the exception in case of failure.
*/
public void throwException() {
getCause().ifPresent(Throwing::sneakyThrow);
}
/**
* Cause for failure or empty optional for success result.
*
* @return Cause for failure or empty optional for success result.
*/
public abstract Optional getCause();
}