From b60ec8345f8b9d9df21fc144eb2a5aff2a38c77b Mon Sep 17 00:00:00 2001 From: Daniel Dietrich Date: Fri, 3 Nov 2017 13:35:36 +0100 Subject: [PATCH] Added onFailure for specific exception types (#2149) --- vavr/src/main/java/io/vavr/control/Try.java | 189 +++++++++++++++--- .../test/java/io/vavr/control/TryTest.java | 14 ++ 2 files changed, 170 insertions(+), 33 deletions(-) diff --git a/vavr/src/main/java/io/vavr/control/Try.java b/vavr/src/main/java/io/vavr/control/Try.java index aab74c7229..4571d70f4b 100644 --- a/vavr/src/main/java/io/vavr/control/Try.java +++ b/vavr/src/main/java/io/vavr/control/Try.java @@ -34,7 +34,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; -import static io.vavr.API.Match; import static io.vavr.control.TryModule.isFatal; import static io.vavr.control.TryModule.sneakyThrow; @@ -582,10 +581,10 @@ default Try mapFailure(Function mappe } /** - * Runs the given checked function if this is a {@code Success}, + * Runs the given checked function if this is a {@link Try.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. + * If this expression is a {@link Try.Failure} then it'll return a new + * {@link Try.Failure} of type R with the original exception. *

* The main use case is chaining checked functions using method references: * @@ -614,9 +613,17 @@ default Try mapTry(CheckedFunction1 mapper) { } } } - + /** - * Consumes the throwable if this is a Failure. + * Consumes the cause if this is a {@link Try.Failure}. + * + *

{@code
+     * // (does not print anything)
+     * Try.success(1).onFailure(System.out::println);
+     *
+     * // prints "java.lang.Error"
+     * Try.failure(new Error()).onFailure(System.out::println);
+     * }
* * @param action An exception consumer * @return this @@ -631,7 +638,45 @@ default Try onFailure(Consumer action) { } /** - * Consumes the value if this is a Success. + * Consumes the cause if this is a {@link Try.Failure} and the cause is instance of {@code X}. + * + *
{@code
+     * // (does not print anything)
+     * Try.success(1).onFailure(Error.class, System.out::println);
+     *
+     * // prints "Error"
+     * Try.failure(new Error())
+     *    .onFailure(RuntimeException.class, x -> System.out.println("Runtime exception"))
+     *    .onFailure(Error.class, x -> System.out.println("Error"));
+     * }
+ * + * @param exceptionType the exception type that is handled + * @param action an excpetion consumer + * @param the exception type that should be handled + * @return this + * @throws NullPointerException if {@code exceptionType} or {@code action} is null + */ + @GwtIncompatible + @SuppressWarnings("unchecked") + default Try onFailure(Class exceptionType, Consumer action) { + Objects.requireNonNull(exceptionType, "exceptionType is null"); + Objects.requireNonNull(action, "action is null"); + if (isFailure() && exceptionType.isAssignableFrom(getCause().getClass())) { + action.accept((X) getCause()); + } + return this; + } + + /** + * Consumes the value if this is a {@link Try.Success}. + * + *
{@code
+     * // prints "1"
+     * Try.success(1).onSuccess(System.out::println);
+     *
+     * // (does not print anything)
+     * Try.failure(new Error()).onSuccess(System.out::println);
+     * }
* * @param action A value consumer * @return this @@ -719,20 +764,33 @@ default Try peek(Consumer action) { * from {@code cause.getClass()}. Otherwise tries to recover the exception of the failure with {@code f}, * i.e. calling {@code Try.of(() -> f.apply((X) getCause())}. * - * @param Exception type - * @param exception The specific exception type that should be handled - * @param f A recovery function taking an exception of type {@code X} + *
{@code
+     * // = Success(13)
+     * Try.of(() -> 27/2).recover(ArithmeticException.class, x -> Integer.MAX_VALUE);
+     *
+     * // = Success(2147483647)
+     * Try.of(() -> 1/0)
+     *    .recover(Error.class, x -> -1)
+     *    .recover(ArithmeticException.class, x -> Integer.MAX_VALUE);
+     *
+     * // = Failure(java.lang.ArithmeticException: / by zero)
+     * Try.of(() -> 1/0).recover(Error.class, x -> Integer.MAX_VALUE);
+     * }
+ * + * @param Exception type + * @param exceptionType The specific exception type that should be handled + * @param f A recovery function taking an exception of type {@code X} * @return a {@code Try} * @throws NullPointerException if {@code exception} is null or {@code f} is null */ @GwtIncompatible @SuppressWarnings("unchecked") - default Try recover(Class exception, Function f) { - Objects.requireNonNull(exception, "exception is null"); + default Try recover(Class exceptionType, Function f) { + Objects.requireNonNull(exceptionType, "exceptionType is null"); Objects.requireNonNull(f, "f is null"); if (isFailure()) { final Throwable cause = getCause(); - if (exception.isAssignableFrom(cause.getClass())) { + if (exceptionType.isAssignableFrom(cause.getClass())) { return Try.of(() -> f.apply((X) cause)); } } @@ -744,20 +802,34 @@ default Try recover(Class exception, Functionwhich returns Try. * If {@link Try#isFailure()} returned by {@code f} function is true it means that recovery cannot take place due to some circumstances. * - * @param Exception type - * @param exception The specific exception type that should be handled - * @param f A recovery function taking an exception of type {@code X} and returning Try as a result of recovery. - * If Try is {@link Try#isSuccess()} then recovery ends up successfully. Otherwise the function was not able to recover. + *
{@code
+     * // = Success(13)
+     * Try.of(() -> 27/2).recoverWith(ArithmeticException.class, x -> Try.success(Integer.MAX_VALUE));
+     *
+     * // = Success(2147483647)
+     * Try.of(() -> 1/0)
+     *    .recoverWith(Error.class, x -> Try.success(-1))
+     *    .recoverWith(ArithmeticException.class, x -> Try.success(Integer.MAX_VALUE));
+     *
+     * // = Failure(java.lang.ArithmeticException: / by zero)
+     * Try.of(() -> 1/0).recoverWith(Error.class, x -> Try.success(Integer.MAX_VALUE));
+     * }
+ * + * @param Exception type + * @param exceptionType The specific exception type that should be handled + * @param f A recovery function taking an exception of type {@code X} and returning Try as a result of recovery. + * If Try is {@link Try#isSuccess()} then recovery ends up successfully. Otherwise the function was not able to recover. * @return a {@code Try} + * @throws NullPointerException if {@code exceptionType} or {@code f} is null */ @GwtIncompatible @SuppressWarnings("unchecked") - default Try recoverWith(Class exception, Function> f){ - Objects.requireNonNull(exception, "exception is null"); + default Try recoverWith(Class exceptionType, Function> f){ + Objects.requireNonNull(exceptionType, "exceptionType is null"); Objects.requireNonNull(f, "f is null"); if(isFailure()){ final Throwable cause = getCause(); - if (exception.isAssignableFrom(cause.getClass())) { + if (exceptionType.isAssignableFrom(cause.getClass())) { try { return narrow(f.apply((X) cause)); } catch (Throwable t) { @@ -768,38 +840,81 @@ default Try recoverWith(Class exception, Function{@code + * // = Success(13) + * Try.of(() -> 27/2).recoverWith(ArithmeticException.class, Try.success(Integer.MAX_VALUE)); + * + * // = Success(2147483647) + * Try.of(() -> 1/0) + * .recoverWith(Error.class, Try.success(-1)) + * .recoverWith(ArithmeticException.class, Try.success(Integer.MAX_VALUE)); + * + * // = Failure(java.lang.ArithmeticException: / by zero) + * Try.of(() -> 1/0).recoverWith(Error.class, Try.success(Integer.MAX_VALUE)); + * } + * + * @param exceptionType the exception type that is recovered + * @param recovered the substitute for a matching {@code Failure} + * @param type of the exception that should be recovered + * @return the given {@code recovered} if this is a {@link Try.Failure} and the cause is of type {@code X}, else {@code this} + * @throws NullPointerException if {@code exceptionType} or {@code recovered} is null + */ @GwtIncompatible - default Try recoverWith(Class exception, Try recovered){ - return (isFailure() && exception.isAssignableFrom(getCause().getClass())) + default Try recoverWith(Class exceptionType, Try recovered){ + Objects.requireNonNull(exceptionType, "exeptionType is null"); + Objects.requireNonNull(recovered, "recovered is null"); + return (isFailure() && exceptionType.isAssignableFrom(getCause().getClass())) ? narrow(recovered) : this; } - /** - * Returns {@code this}, if this is a {@code Success} or this is a {@code Failure} and the cause is not assignable - * from {@code cause.getClass()}. Otherwise returns a {@code Success} containing the given {@code value}. + * Returns {@code this}, if this is a {@link Try.Success} or this is a {@code Failure} and the cause is not assignable + * from {@code cause.getClass()}. Otherwise returns a {@link Try.Success} containing the given {@code value}. + * + *
{@code
+     * // = Success(13)
+     * Try.of(() -> 27/2).recover(ArithmeticException.class, Integer.MAX_VALUE);
+     *
+     * // = Success(2147483647)
+     * Try.of(() -> 1/0)
+     *    .recover(Error.class, -1);
+     *    .recover(ArithmeticException.class, Integer.MAX_VALUE);
+     *
+     * // = Failure(java.lang.ArithmeticException: / by zero)
+     * Try.of(() -> 1/0).recover(Error.class, Integer.MAX_VALUE);
+     * }
* - * @param Exception type - * @param exception The specific exception type that should be handled - * @param value A value that is used in case of a recovery + * @param Exception type + * @param exceptionType The specific exception type that should be handled + * @param value A value that is used in case of a recovery * @return a {@code Try} * @throws NullPointerException if {@code exception} is null */ @GwtIncompatible - default Try recover(Class exception, T value) { - Objects.requireNonNull(exception, "exception is null"); - return (isFailure() && exception.isAssignableFrom(getCause().getClass())) + default Try recover(Class exceptionType, T value) { + Objects.requireNonNull(exceptionType, "exceptionType is null"); + return (isFailure() && exceptionType.isAssignableFrom(getCause().getClass())) ? Try.success(value) : 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))}. * + *
{@code
+     * // = Success(13)
+     * Try.of(() -> 27/2).recover(x -> Integer.MAX_VALUE);
+     *
+     * // = Success(2147483647)
+     * Try.of(() -> 1/0).recover(x -> Integer.MAX_VALUE);
+     * }
+ * * @param f A recovery function taking a Throwable * @return a {@code Try} * @throws NullPointerException if {@code f} is null @@ -818,6 +933,14 @@ default Try recover(Function f) { * i.e. calling {@code f.apply(cause.getCause())}. If an error occurs recovering a Failure, then the new Failure is * returned. * + *
{@code
+     * // = Success(13)
+     * Try.of(() -> 27/2).recoverWith(x -> Try.success(Integer.MAX_VALUE));
+     *
+     * // = Success(2147483647)
+     * Try.of(() -> 1/0).recoverWith(x -> Try.success(Integer.MAX_VALUE));
+     * }
+ * * @param f A recovery function taking a Throwable * @return a {@code Try} * @throws NullPointerException if {@code f} is null diff --git a/vavr/src/test/java/io/vavr/control/TryTest.java b/vavr/src/test/java/io/vavr/control/TryTest.java index 0388ceac45..907a2cbf6e 100644 --- a/vavr/src/test/java/io/vavr/control/TryTest.java +++ b/vavr/src/test/java/io/vavr/control/TryTest.java @@ -920,6 +920,20 @@ public void shouldConsumeThrowableWhenCallingOnFailureGivenFailure() { assertThat(result[0]).isEqualTo(OK); } + @Test + public void shouldConsumeThrowableWhenCallingOnFailureWithMatchingExceptionTypeGivenFailure() { + final String[] result = new String[] { FAILURE }; + failure().onFailure(RuntimeException.class, x -> result[0] = OK); + assertThat(result[0]).isEqualTo(OK); + } + + @Test + public void shouldNotConsumeThrowableWhenCallingOnFailureWithNonMatchingExceptionTypeGivenFailure() { + final String[] result = new String[] { OK }; + failure().onFailure(Error.class, x -> result[0] = FAILURE); + assertThat(result[0]).isEqualTo(OK); + } + // -- transform @Test(expected = NullPointerException.class)