+ * If this Try is successful, nothing happens, and this Try is returned
+ * unchanged. If this Try contains an error that is doesn't satisfy the
+ * predicate, nothing happens, and this Try is returned unchanged. If this Try
+ * contains an error that satisfies the predicate, the error consumer is
+ * executed.
+ *
+ *
+ *
+ * This method is useful for handling specific exception types differently, such
+ * as logging at different levels, recording metrics, or performing
+ * type-specific cleanup operations.
+ *
+ *
+ * @param predicate the class of the exception type to match against
+ * @param errorConsumer consumer to execute if the error matches the specified
+ * type
+ * @return this Try instance for method chaining
+ * @throws NullPointerException if predicate is null
+ * @see #onFailure(Consumer)
+ * @see #onFailure(Consumer, Class[])
+ * @see #recover(Class, Function)
+ */
+ public Try onFailure(
+ Predicate super Exception> predicate,
+ Consumer super Exception> errorConsumer
+ ) {
+ Objects.requireNonNull(predicate);
+ if (isFailure() && predicate.test(getError())) {
+ return onHandle(null, errorConsumer);
+ }
+ return this;
+ }
+
+ /**
+ * Executes the provided consumer if this {@code Try} is a failure and the error
+ * matches the specified exception type.
+ *
+ * If this Try is successful, nothing happens, and this Try is returned
+ * unchanged. If this Try contains an error that is NOT an instance of the
+ * specified exception type, nothing happens, and this Try is returned
+ * unchanged. If this Try contains an error that IS an instance of the specified
+ * exception type, the error consumer is executed.
+ *
+ *
+ *
+ * This method is useful for handling specific exception types differently, such
+ * as logging at different levels, recording metrics, or performing
+ * type-specific cleanup operations.
+ *
+ *
+ *
+ * Example usage:
+ *
+ *
+ *
+ * {
+ * @code
+ * // Log different exception types at different levels
+ * Try<Data> result = Try.to(() -> fetchData())
+ * .onFailure(IOException.class, ex -> logger.error("I/O error: {}", ex.getMessage()))
+ * .onFailure(TimeoutException.class, ex -> logger.warn("Timeout: {}", ex.getMessage()))
+ * .onFailure(ex -> logger.error("Unexpected error", ex));
+ *
+ * // Record type-specific metrics
+ * Try<Response> apiResult = Try.to(() -> callApi())
+ * .onFailure(TimeoutException.class, ex -> metrics.increment("api.timeout"))
+ * .onFailure(IOException.class, ex -> metrics.increment("api.network_error"));
+ * }
+ *
+ *
+ * @param exceptionType the class of the exception type to match against
+ * @param errorConsumer consumer to execute if the error matches the specified
+ * type
+ * @return this Try instance for method chaining
+ * @throws NullPointerException if exceptionType is null
+ * @see #onFailure(Consumer)
+ * @see #onFailure(Consumer, Class[])
+ * @see #recover(Class, Function)
+ */
+ public Try onFailure(
+ Class extends Exception> exceptionType,
+ Consumer super Exception> errorConsumer
+ ) {
+ Objects.requireNonNull(exceptionType);
+ return onFailure(exceptionType::isInstance, errorConsumer);
+ }
+
+ /**
+ * Executes the provided consumer if this {@code Try} is a failure and the error
+ * matches any of the specified exception types.
+ *
+ * This method provides a convenient way to handle multiple exception types
+ * using a single error consumer. If this Try is successful, nothing happens,
+ * and this Try is returned unchanged. If this Try contains an error that does
+ * NOT match any of the specified exception types, nothing happens, and this Try
+ * is returned unchanged. If this Try contains an error that matches at least
+ * one of the specified exception types, the error consumer is executed.
+ *
+ *
+ *
+ * Null elements in the varargs array are automatically filtered out and ignored
+ * during exception type matching.
+ *
+ *
+ *
+ * Example usage:
+ *
+ *
+ *
+ * {
+ * @code
+ * // Handle multiple I/O-related exceptions with a single handler
+ * Try<String> result = Try.to(() -> readFile())
+ * .onFailure(
+ * ex -> logger.error("I/O operation failed: {}", ex.getMessage()),
+ * IOException.class,
+ * FileNotFoundException.class,
+ * SocketTimeoutException.class
+ * );
+ *
+ * // Combine different error handling strategies
+ * Try<Data> processed = Try.to(() -> processData())
+ * .onFailure(
+ * ex -> metrics.increment("errors.io"),
+ * IOException.class,
+ * TimeoutException.class
+ * )
+ * .onFailure(
+ * ex -> metrics.increment("errors.validation"),
+ * ValidationException.class,
+ * ParseException.class
+ * )
+ * .onFailure(ex -> metrics.increment("errors.other"));
+ * }
+ *
+ *
+ * @param errorConsumer consumer to execute if the error matches any of the
+ * specified types
+ * @param exceptionTypes varargs array of exception types to match against; null
+ * elements are ignored
+ * @return this Try instance for method chaining
+ * @throws NullPointerException if exceptionTypes array itself is null
+ * @see #onFailure(Consumer)
+ * @see #onFailure(Class, Consumer)
+ * @see #recover(Function, Class[])
+ */
+ @SafeVarargs
+ @Preview
+ public final Try onFailure(
+ Consumer super Exception> errorConsumer,
+ Class extends Exception>... exceptionTypes
+ ) {
+ Objects.requireNonNull(exceptionTypes);
+ return onFailure(
+ e -> Arrays.stream(exceptionTypes)
+ .filter(Objects::nonNull)
+ .anyMatch(exceptionType -> exceptionType.isInstance(e)),
+ errorConsumer
+ );
+ }
+
/**
* Converts this {@code Try} into a {@code Result} object.
*
diff --git a/src/test/java/org/zeplinko/commons/lang/ext/core/TryTest.java b/src/test/java/org/zeplinko/commons/lang/ext/core/TryTest.java
index e10d61b..89321ef 100644
--- a/src/test/java/org/zeplinko/commons/lang/ext/core/TryTest.java
+++ b/src/test/java/org/zeplinko/commons/lang/ext/core/TryTest.java
@@ -11,6 +11,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
+import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
@@ -320,7 +321,6 @@ void test_givenFailure_whenFlatMapIsCalled_thenSameFailureIsReturned() {
@Test
void test_givenNullMapper_whenRecoverIsCalled_thenExceptionIsThrown() {
Try failure = Try.failure(new RuntimeException("error"));
-
@SuppressWarnings("DataFlowIssue")
NullPointerException nullPointerException = assertThrows(
NullPointerException.class,
@@ -348,6 +348,118 @@ void test_givenSuccess_whenRecoverIsCalled_thenSameSuccessIsReturned() {
assertEquals("data", result.getData());
}
+ @Test
+ void test_givenNullMapper_whenPredicateRecoverIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings("DataFlowIssue")
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.recover((Predicate super Exception>) IllegalArgumentException.class::isInstance, null)
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenNullPredicate_whenPredicateRecoverIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings("DataFlowIssue")
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.recover((Predicate super Exception>) null, e -> Try.success("ignored"))
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenSuccess_whenPredicateRecoverIsCalled_thenSameSuccessIsReturned() {
+ Try success = Try.success("data");
+ Try result = success.recover(IllegalArgumentException.class::isInstance, e -> Try.success("ignored"));
+
+ assertTrue(result.isSuccess());
+ assertEquals("data", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWhichDoesNotSatisfyThePredicate_whenPredicateRecoverIsCalled_thenSameTryIsReturned() {
+ Exception error = new RuntimeException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(IllegalArgumentException.class::isInstance, e -> Try.success("recovered"));
+
+ assertTrue(result.isFailure());
+ assertSame(error, result.getError());
+ }
+
+ @Test
+ void test_givenFailureWhichSatisfiesThePredicate_whenPredicateRecoverIsCalled_thenSameTryIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(IllegalArgumentException.class::isInstance, e -> Try.success("recovered"));
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenNullExceptionType_whenRecoverWithExceptionTypeIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings("DataFlowIssue")
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.recover((Class) null, e -> Try.success(0))
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenSuccess_whenRecoverWithExceptionTypeIsCalled_thenSameSuccessIsReturned() {
+ Try success = Try.success("data");
+ Try result = success.recover(RuntimeException.class, e -> Try.success("recovered"));
+
+ assertTrue(result.isSuccess());
+ assertEquals("data", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithNonMatchingException_whenRecoverWithExceptionTypeIsCalled_thenSameFailureIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(IllegalStateException.class, e -> Try.success("recovered"));
+
+ assertTrue(result.isFailure());
+ assertSame(error, result.getError());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingException_whenRecoverWithExceptionTypeIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(IllegalArgumentException.class, e -> Try.success("recovered"));
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingExceptionSubclass_whenRecoverWithExceptionTypeIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(RuntimeException.class, e -> Try.success("recovered"));
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingException_whenRecoverWithExceptionTypeReturnsFailure_thenNewFailureIsReturned() {
+ Exception originalError = new IllegalArgumentException("original");
+ Exception newError = new IllegalStateException("new error");
+ Try failure = Try.failure(originalError);
+ Try result = failure.recover(IllegalArgumentException.class, e -> Try.failure(newError));
+
+ assertTrue(result.isFailure());
+ assertSame(newError, result.getError());
+ }
+
@Test
void test_givenNullMapper_whenTransformIsCalled_thenExceptionIsThrown() {
Try success = Try.success(10);
@@ -440,7 +552,7 @@ void test_givenFailure_whenOnFailureIsCalled_thenConsumerIsExecuted() {
}
@Test
- void test_givenSuccess_whenOnFailureIsCalled_thenConsumerIsExecuted() {
+ void test_givenSuccess_whenOnFailureIsCalled_thenConsumerIsNotExecuted() {
Try success = Try.success("data");
StringBuilder consumed = new StringBuilder();
Try returnedTry = success.onFailure(e -> consumed.append(e.getMessage()));
@@ -449,6 +561,368 @@ void test_givenSuccess_whenOnFailureIsCalled_thenConsumerIsExecuted() {
assertEquals(0, consumed.length());
}
+ @Test
+ void test_givenNullPredicate_whenPredicateOnFailureIsCalled_thenThrowsException() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ StringBuilder consumed = new StringBuilder();
+ Assertions.assertThrows(
+ NullPointerException.class,
+ () -> failure.onFailure((Predicate super Exception>) null, e -> consumed.append(e.getMessage()))
+ );
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenSuccess_whenPredicateOnFailureIsCalled_thenConsumerIsNotExecuted() {
+ Try success = Try.success("data");
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = success
+ .onFailure(IllegalArgumentException.class::isInstance, e -> consumed.append(e.getMessage()));
+
+ assertSame(success, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWhichDoesNotSatisfyPredicate_whenPredicateOnFailureIsCalled_thenConsumerIsNotExecuted() {
+ Exception error = new RuntimeException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure
+ .onFailure(IllegalArgumentException.class::isInstance, e -> consumed.append(e.getMessage()));
+
+ assertSame(failure, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWhichSatisfiesPredicate_whenPredicateOnFailureIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure
+ .onFailure(IllegalArgumentException.class::isInstance, e -> consumed.append(e.getMessage()));
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ // ========== Tests for single exception type onFailure method ==========
+
+ @Test
+ void test_givenNullExceptionType_whenOnFailureWithExceptionTypeIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings("DataFlowIssue")
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.onFailure((Class) null, e -> {
+ })
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenSuccess_whenOnFailureWithExceptionTypeIsCalled_thenConsumerIsNotExecuted() {
+ Try success = Try.success("data");
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = success.onFailure(RuntimeException.class, e -> consumed.append(e.getMessage()));
+
+ assertSame(success, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWithNonMatchingException_whenOnFailureWithExceptionTypeIsCalled_thenConsumerIsNotExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(IllegalStateException.class, e -> consumed.append(e.getMessage()));
+
+ assertSame(failure, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingException_whenOnFailureWithExceptionTypeIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure
+ .onFailure(IllegalArgumentException.class, e -> consumed.append(e.getMessage()));
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingExceptionSubclass_whenOnFailureWithExceptionTypeIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(RuntimeException.class, e -> consumed.append(e.getMessage()));
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenChainedOnFailureCallsWithDifferentTypes_whenFailureMatches_thenOnlyMatchingConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("validation error");
+ Try failure = Try.failure(error);
+ List log = new ArrayList<>();
+
+ Try result = failure
+ .onFailure(IllegalStateException.class, e -> log.add("state error"))
+ .onFailure(IllegalArgumentException.class, e -> log.add("argument error"))
+ .onFailure(NullPointerException.class, e -> log.add("null error"));
+
+ assertSame(failure, result);
+ assertEquals(1, log.size());
+ assertEquals("argument error", log.get(0));
+ }
+
+ @Test
+ void test_givenChainedOnFailureWithCatchAll_whenFailureOccurs_thenAllMatchingConsumersAreExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ List log = new ArrayList<>();
+
+ Try result = failure
+ .onFailure(IllegalArgumentException.class, e -> log.add("specific"))
+ .onFailure(RuntimeException.class, e -> log.add("general"))
+ .onFailure(e -> log.add("catch-all"));
+
+ assertSame(failure, result);
+ assertEquals(3, log.size());
+ assertEquals("specific", log.get(0));
+ assertEquals("general", log.get(1));
+ assertEquals("catch-all", log.get(2));
+ }
+
+ // ========== Tests for varargs exception types onFailure method ==========
+
+ @Test
+ void test_givenNullExceptionTypesArray_whenOnFailureWithVarargsIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings({ "DataFlowIssue", "unchecked" })
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.onFailure(e -> {
+ }, (Class extends Exception>[]) null)
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenSuccess_whenOnFailureWithVarargsIsCalled_thenConsumerIsNotExecuted() {
+ Try success = Try.success("data");
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = success.onFailure(
+ e -> consumed.append(e.getMessage()),
+ RuntimeException.class,
+ IllegalArgumentException.class
+ );
+
+ assertSame(success, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWithNoMatchingException_whenOnFailureWithVarargsIsCalled_thenConsumerIsNotExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWithEmptyVarargsArray_whenOnFailureWithVarargsIsCalled_thenConsumerIsNotExecuted() {
+ Exception error = new RuntimeException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ new Class[0]
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenFailureWithSingleMatchingException_whenOnFailureWithVarargsIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ IllegalArgumentException.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureWithFirstMatchingException_whenOnFailureWithVarargsIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ IllegalArgumentException.class,
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureWithSecondMatchingException_whenOnFailureWithVarargsIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalStateException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ IllegalArgumentException.class,
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureWithLastMatchingException_whenOnFailureWithVarargsIsCalled_thenConsumerIsExecuted() {
+ Exception error = new NullPointerException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ IllegalArgumentException.class,
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingExceptionSubclass_whenOnFailureWithVarargsIsCalled_thenConsumerIsExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ RuntimeException.class,
+ Exception.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureAndNullExceptionTypesInVarargs_whenOnFailureWithVarargsIsCalled_thenNullsAreIgnored() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ @SuppressWarnings("unchecked")
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ null,
+ IllegalArgumentException.class,
+ null
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals("error", consumed.toString());
+ }
+
+ @Test
+ void test_givenFailureAndAllNullExceptionTypesInVarargs_whenOnFailureWithVarargsIsCalled_thenConsumerIsNotExecuted() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ StringBuilder consumed = new StringBuilder();
+ @SuppressWarnings("unchecked")
+ Try returnedTry = failure.onFailure(
+ e -> consumed.append(e.getMessage()),
+ null,
+ null
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals(0, consumed.length());
+ }
+
+ @Test
+ void test_givenMultipleIOExceptions_whenOnFailureWithVarargsIsCalled_thenSingleHandlerIsExecuted() {
+ Exception error = new java.io.FileNotFoundException("file not found");
+ Try failure = Try.failure(error);
+ List log = new ArrayList<>();
+ Try returnedTry = failure.onFailure(
+ e -> log.add("I/O error: " + e.getMessage()),
+ java.io.IOException.class,
+ java.io.FileNotFoundException.class,
+ java.net.SocketTimeoutException.class
+ );
+
+ assertSame(failure, returnedTry);
+ assertEquals(1, log.size());
+ assertEquals("I/O error: file not found", log.get(0));
+ }
+
+ @Test
+ void test_givenChainedOnFailureCallsWithVarargs_whenDifferentExceptionGroups_thenCorrectHandlerIsExecuted() {
+ Exception error = new IllegalArgumentException("validation error");
+ Try failure = Try.failure(error);
+ List log = new ArrayList<>();
+
+ Try result = failure
+ .onFailure(
+ e -> log.add("io"),
+ java.io.IOException.class,
+ java.util.concurrent.TimeoutException.class
+ )
+ .onFailure(
+ e -> log.add("validation"),
+ IllegalArgumentException.class,
+ IllegalStateException.class
+ );
+
+ assertSame(failure, result);
+ assertEquals(1, log.size());
+ assertEquals("validation", log.get(0));
+ }
+
+ @Test
+ void test_givenRealWorldLoggingScenario_whenOnFailureWithMultipleTypes_thenCorrectLoggingOccurs() {
+ Exception error = new java.io.IOException("network error");
+ Try failure = Try.failure(error);
+ List logMessages = new ArrayList<>();
+
+ Try result = failure
+ .onFailure(java.io.IOException.class, e -> logMessages.add("ERROR: " + e.getMessage()))
+ .onFailure(java.util.concurrent.TimeoutException.class, e -> logMessages.add("WARN: " + e.getMessage()))
+ .onFailure(e -> logMessages.add("UNKNOWN: " + e.getMessage()));
+
+ assertSame(failure, result);
+ assertEquals(2, logMessages.size());
+ assertEquals("ERROR: network error", logMessages.get(0));
+ assertEquals("UNKNOWN: network error", logMessages.get(1));
+ }
+
@Test
void test_givenSuccess_whenToResultIsCalled_thenResultIsReturned() {
Try success = Try.success("data");
@@ -468,6 +942,224 @@ void test_givenFailure_whenToResultIsCalled_thenResultIsReturned() {
assertSame(error, result.getError());
}
+ // ========== Tests for varargs recover method ==========
+
+ @Test
+ void test_givenNullFailureMapper_whenRecoverWithVarargsIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings("DataFlowIssue")
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.recover(null, RuntimeException.class, IllegalArgumentException.class)
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenNullExceptionTypesArray_whenRecoverWithVarargsIsCalled_thenExceptionIsThrown() {
+ Try failure = Try.failure(new RuntimeException("error"));
+ @SuppressWarnings({ "DataFlowIssue", "unchecked" })
+ NullPointerException nullPointerException = assertThrows(
+ NullPointerException.class,
+ () -> failure.recover(e -> Try.success(0), (Class extends Exception>[]) null)
+ );
+ assertNotNull(nullPointerException);
+ }
+
+ @Test
+ void test_givenSuccess_whenRecoverWithVarargsIsCalled_thenSameSuccessIsReturned() {
+ Try success = Try.success("data");
+ Try result = success.recover(
+ e -> Try.success("recovered"),
+ RuntimeException.class,
+ IllegalArgumentException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("data", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithNoMatchingException_whenRecoverWithVarargsIsCalled_thenSameFailureIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertTrue(result.isFailure());
+ assertSame(error, result.getError());
+ }
+
+ @Test
+ void test_givenFailureWithEmptyVarargsArray_whenRecoverWithVarargsIsCalled_thenSameFailureIsReturned() {
+ Exception error = new RuntimeException("error");
+ Try failure = Try.failure(error);
+ @SuppressWarnings("unchecked")
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ new Class[0] // Explicitly pass empty array to call varargs version
+ );
+
+ assertTrue(result.isFailure());
+ assertSame(error, result.getError());
+ }
+
+ @Test
+ void test_givenFailureWithSingleMatchingException_whenRecoverWithVarargsIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ IllegalArgumentException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithFirstMatchingException_whenRecoverWithVarargsIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ IllegalArgumentException.class,
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithSecondMatchingException_whenRecoverWithVarargsIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new IllegalStateException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ IllegalArgumentException.class,
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithLastMatchingException_whenRecoverWithVarargsIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new NullPointerException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ IllegalArgumentException.class,
+ IllegalStateException.class,
+ NullPointerException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingExceptionSubclass_whenRecoverWithVarargsIsCalled_thenRecoveredTryIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ RuntimeException.class,
+ Exception.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureWithMatchingException_whenRecoverWithVarargsReturnsFailure_thenNewFailureIsReturned() {
+ Exception originalError = new IllegalArgumentException("original");
+ Exception newError = new IllegalStateException("new error");
+ Try failure = Try.failure(originalError);
+ Try result = failure.recover(
+ e -> Try.failure(newError),
+ IllegalArgumentException.class,
+ NullPointerException.class
+ );
+
+ assertTrue(result.isFailure());
+ assertSame(newError, result.getError());
+ }
+
+ @Test
+ void test_givenFailureAndNullExceptionTypesInVarargs_whenRecoverWithVarargsIsCalled_thenNullsAreIgnored() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ @SuppressWarnings("unchecked")
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ null,
+ IllegalArgumentException.class,
+ null
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("recovered", result.getData());
+ }
+
+ @Test
+ void test_givenFailureAndAllNullExceptionTypesInVarargs_whenRecoverWithVarargsIsCalled_thenSameFailureIsReturned() {
+ Exception error = new IllegalArgumentException("error");
+ Try failure = Try.failure(error);
+ @SuppressWarnings("unchecked")
+ Try result = failure.recover(
+ e -> Try.success("recovered"),
+ null,
+ null
+ );
+
+ assertTrue(result.isFailure());
+ assertSame(error, result.getError());
+ }
+
+ @Test
+ void test_givenMultipleIOExceptions_whenRecoverWithVarargsIsCalled_thenSingleHandlerIsApplied() {
+ Exception error = new java.io.FileNotFoundException("file not found");
+ Try failure = Try.failure(error);
+ Try result = failure.recover(
+ e -> Try.success("default content"),
+ java.io.IOException.class,
+ java.io.FileNotFoundException.class,
+ java.net.SocketTimeoutException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("default content", result.getData());
+ }
+
+ @Test
+ void test_givenChainedRecoverCallsWithVarargs_whenDifferentExceptionGroups_thenCorrectHandlerIsApplied() {
+ Exception error = new IllegalArgumentException("validation error");
+ Try failure = Try.failure(error);
+ Try result = failure
+ .recover(
+ e -> Try.success("io recovered"),
+ java.io.IOException.class,
+ java.util.concurrent.TimeoutException.class
+ )
+ .recover(
+ e -> Try.success("validation recovered"),
+ IllegalArgumentException.class,
+ IllegalStateException.class
+ );
+
+ assertTrue(result.isSuccess());
+ assertEquals("validation recovered", result.getData());
+ }
+
@MethodSource("provideHashcodeCoverageCases")
@ParameterizedTest
void test_givenTry_whenHashcodeIsInvoked_thenRespectiveOutcomeIsReturned(Try> tryObject, int expectedHashcode) {