diff --git a/core-java-modules/core-java-streams-5/pom.xml b/core-java-modules/core-java-streams-5/pom.xml index fde48978c9..dc97d81b3d 100644 --- a/core-java-modules/core-java-streams-5/pom.xml +++ b/core-java-modules/core-java-streams-5/pom.xml @@ -38,6 +38,11 @@ 3.12.0 test + + io.vavr + vavr + ${vavr.version} + @@ -66,6 +71,7 @@ 12 12 + 0.10.2 \ No newline at end of file diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomCollector.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomCollector.java new file mode 100644 index 0000000000..dea3d3f44c --- /dev/null +++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomCollector.java @@ -0,0 +1,38 @@ +package com.baeldung.aggregateexception; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collector; + +public class CustomCollector { + private final List results = new ArrayList<>(); + private final List exceptions = new ArrayList<>(); + + public static Collector> of(Function mapper) { + return Collector.of( + CustomCollector::new, + (collector, item) -> { + try { + R result = mapper.apply(item); + collector.results.add(result); + } catch (Exception e) { + collector.exceptions.add(e); + } + }, + (left, right) -> { + left.results.addAll(right.results); + left.exceptions.addAll(right.exceptions); + return left; + } + ); + } + + public List getResults() { + return results; + } + + public List getExceptions() { + return exceptions; + } +} diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomMapper.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomMapper.java new file mode 100644 index 0000000000..920c963337 --- /dev/null +++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/CustomMapper.java @@ -0,0 +1,17 @@ +package com.baeldung.aggregateexception; + +import com.baeldung.aggregateexception.entity.Result; + +import java.util.function.Function; + +public class CustomMapper { + public static Function> mapper(Function func) { + return arg -> { + try { + return new Result(func.apply(arg)); + } catch (Exception e) { + return new Result(e); + } + }; + } +} diff --git a/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/entity/Result.java b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/entity/Result.java new file mode 100644 index 0000000000..b723c3e510 --- /dev/null +++ b/core-java-modules/core-java-streams-5/src/main/java/com/baeldung/aggregateexception/entity/Result.java @@ -0,0 +1,26 @@ +package com.baeldung.aggregateexception.entity; + +import java.util.Optional; + +public class Result { + private Optional result; + private Optional exception; + + public Result(R result) { + this.result = Optional.of(result); + this.exception = Optional.empty(); + } + + public Result(E exception) { + this.exception = Optional.of(exception); + this.result = Optional.empty(); + } + + public Optional getResult() { + return result; + } + + public Optional getException() { + return exception; + } +} diff --git a/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/aggregateexception/AggregateExceptionHandlerUnitTest.java b/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/aggregateexception/AggregateExceptionHandlerUnitTest.java new file mode 100644 index 0000000000..6410645d2a --- /dev/null +++ b/core-java-modules/core-java-streams-5/src/test/java/com/baeldung/aggregateexception/AggregateExceptionHandlerUnitTest.java @@ -0,0 +1,150 @@ +package com.baeldung.aggregateexception; + +import com.baeldung.aggregateexception.entity.Result; +import io.vavr.control.Either; +import io.vavr.control.Try; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + + +public class AggregateExceptionHandlerUnitTest { + private static final Logger logger = LoggerFactory.getLogger(AggregateExceptionHandlerUnitTest.class); + + @Test + public void givenExtractedMethod_whenFoundEx_thenSuppressExIntoRuntimeEx() { + String[] strings = {"1", "2", "3", "a", "b", "c"}; + RuntimeException runEx = Arrays.stream(strings) + .map(str -> callProcessThrowsExAndNoOutput(str)) + .filter(Objects::nonNull) + .reduce(new RuntimeException("Errors Occurred"), (o1, o2) -> { + o1.addSuppressed(o2); + return o1; + }); + processExceptions(runEx); + assertEquals("Errors Occurred", runEx.getMessage()); + assertEquals(3, runEx.getSuppressed().length); + } + @Test + public void givenTryCatchInPipeline_whenFoundEx_thenSuppressExIntoRuntimeEx() { + String[] strings = {"1", "2", "3", "a", "b", "c"}; + RuntimeException runEx = Arrays.stream(strings).map(str -> { + try { + processThrowsExAndNoOutput(str); + return null; + } catch (RuntimeException e) { + return e; + } + }).filter(Objects::nonNull) + .collect(Collectors.collectingAndThen(Collectors.toList(), list -> { + RuntimeException runtimeException = new RuntimeException("Errors Occurred"); + list.forEach(runtimeException::addSuppressed); + return runtimeException; + })); + processExceptions(runEx); + assertEquals("Errors Occurred", runEx.getMessage()); + assertEquals(3, runEx.getSuppressed().length); + } + + @Test + public void givenProcessMethod_whenStreamResultHasExAndOutput_thenHandleExceptionListAndOutputList() { + List strings = List.of("1", "2", "3", "a", "b", "c"); + Map map = strings.stream() + .map(s -> processReturnsExAndOutput(s)) + .collect(Collectors.partitioningBy(o -> o instanceof RuntimeException, Collectors.toList())); + + List exceptions = (List)map.getOrDefault(Boolean.TRUE, List.of()); + List results = (List)map.getOrDefault(Boolean.FALSE, List.of()); + handleExceptionsAndOutputs(exceptions, results); + } + + @Test + public void givenCustomMapper_whenStreamResultHasExAndSuccess_thenHandleExceptionListAndOutputList() { + List strings = List.of("1", "2", "3", "a", "b", "c"); + strings.stream() + .map(CustomMapper.mapper(Integer::parseInt)) + .collect(Collectors.collectingAndThen(Collectors.toList(), list -> handleErrorsAndOutputForResult(list))); + } + + @Test + public void givenCustomCollector_whenStreamResultHasExAndSuccess_thenHandleAggrExceptionAndResults() { + String[] strings = {"1", "2", "3", "a", "b", "c"}; + Arrays.stream(strings) + .collect(Collectors.collectingAndThen(CustomCollector.of(Integer::parseInt), + col -> handleExAndResults(col.getExceptions(), col.getResults()))); + } + + @Test + public void givenVavrEitherAndTry_whenStreamResultHasExAndSuccess_thenHandleExceptionListAndOutputList() { + List strings = List.of("1", "2", "3", "a", "b", "c"); + strings.stream() + .map(str -> Try.of(() -> Integer.parseInt(str)).toEither()) + .collect(Collectors.collectingAndThen(Collectors.partitioningBy(Either::isLeft, Collectors.toList()), + map -> handleErrorsAndOutputForEither(map))); + } + + private static void processThrowsExAndNoOutput(String input) { + //throw exception when input is "a", "b", "c" + if (input.matches("[a-c]")) { + throw new RuntimeException("Downstream method throws exception for " + input); + } + } + private static RuntimeException callProcessThrowsExAndNoOutput(String input) { + try { + processThrowsExAndNoOutput(input); + return null; + } catch (RuntimeException e) { + return e; + } + } + + private static Object processReturnsExAndOutput(String input) { + logger.info("call a downstream method that returns an Integer"); + try { + return Integer.parseInt(input); + } catch (Exception e) { + return new RuntimeException("Exception in processWithReturnOutput for " + input, e); + } + } + + private static void processExceptions(Throwable throwable) { + logger.error("Process Exception" + throwable.getMessage()); + } + + private static void handleExceptionsAndOutputs(List exs, List output) { + logger.info("number of exceptions " + exs.size() + " number of outputs " + output.size()); + } + + private static String handleExAndResults(List ex, List results ) { + logger.info("handle aggregated exceptions and results" + ex.size() + " " + results.size()); + return "Exceptions and Results Handled"; + } + + private static String handleErrorsAndOutputForEither(Map>> map) { + logger.info("handle errors and output"); + map.getOrDefault(Boolean.TRUE, List.of()).forEach(either -> logger.error("Process Exception " + either.getLeft())); + + map.getOrDefault(Boolean.FALSE, List.of()).forEach(either -> logger.info("Process Result " + either.get())); + return "Errors and Output Handled"; + } + + private static String handleErrorsAndOutputForResult(List> successAndErrors) { + logger.info("handle errors and output"); + successAndErrors.forEach(result -> { + if (result.getException().isPresent()) { + logger.error("Process Exception " + result.getException().get()); + } else { + logger.info("Process Result" + result.getResult().get()); + } + }); + return "Errors and Output Handled"; + } +} \ No newline at end of file