From ed329553e5ccd26c481582fae654f1674fd083a3 Mon Sep 17 00:00:00 2001 From: Matthias Jaros Date: Sun, 2 Aug 2026 19:07:52 +0200 Subject: Added PollState to have a wrapper instead of returning null --- src/main/java/com/it_jaros/jscanner/PollState.java | 13 +++++++ src/main/java/com/it_jaros/jscanner/Scanner.java | 45 ++++++++++------------ 2 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/it_jaros/jscanner/PollState.java (limited to 'src') diff --git a/src/main/java/com/it_jaros/jscanner/PollState.java b/src/main/java/com/it_jaros/jscanner/PollState.java new file mode 100644 index 0000000..474c055 --- /dev/null +++ b/src/main/java/com/it_jaros/jscanner/PollState.java @@ -0,0 +1,13 @@ +package com.it_jaros.jscanner; + +/** + * A sealed interface representing the three possible outcomes of a CompletionService + * poll operation: successful result, task failure, or not ready yet. + * Makes error handling explicit — Failure and Unavailable are distinct and compile-time + * required to handle via exhaustiveness checking in switch statements. + */ +public sealed interface PollState { + record Success(T value) implements PollState {} + record Failure(Throwable error) implements PollState {} + record Unavailable() implements PollState {} +} diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java index e3c886b..fd6c0e5 100644 --- a/src/main/java/com/it_jaros/jscanner/Scanner.java +++ b/src/main/java/com/it_jaros/jscanner/Scanner.java @@ -77,11 +77,12 @@ public class Scanner implements AutoCloseable { // with whatever is there already while (state.running().get() || state.inPipeline().get() > 0) { try { - ScanResult result = getHostResult(state); - if (result == null) { - continue; + PollState poll = getHostResult(state); + if (poll instanceof PollState.Success(ScanResult value)) { + consumer.accept(value); + } else if (poll instanceof PollState.Failure(Throwable error)) { + System.err.printf("Host task failed: %s%n", error.getMessage()); } - consumer.accept(result); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } @@ -90,25 +91,21 @@ public class Scanner implements AutoCloseable { scan.stop(); } - private ScanResult getHostResult(ProducerState state) throws InterruptedException { - // let's check for results and give add them to our scan data holder object + private PollState getHostResult(ProducerState state) throws InterruptedException { Future finishedHost = state.completionService().poll(10, TimeUnit.MILLISECONDS); if (finishedHost == null) { - return null; + return new PollState.Unavailable<>(); } - ScanResult result = null; try { - result = finishedHost.get(); + ScanResult result = finishedHost.get(); + return new PollState.Success<>(result); } catch (ExecutionException e) { - System.err.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage()); + return new PollState.Failure<>(e.getCause()); } finally { - // No matter what happens we have to free the resources after getting ScanResult state.activeWorkers().release(); state.inPipeline().decrementAndGet(); } - - return result; } /** @@ -251,12 +248,12 @@ public class Scanner implements AutoCloseable { final PortResultAccumulator accumulator = new PortResultAccumulator(host); while (!cancelled && (state.running().get() || state.inPipeline().get() > 0)) { try { - PortResult portResult = getPortResult(state); - if (portResult == null) { - continue; + PollState poll = getPortResult(state); + if (poll instanceof PollState.Success(PortResult value)) { + accumulator.add(value); + } else if (poll instanceof PollState.Failure(Throwable error)) { + System.err.printf("Port task failed: %s -> %s%n", error.getClass().getSimpleName(), error.getMessage()); } - - accumulator.add(portResult); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } @@ -275,23 +272,23 @@ public class Scanner implements AutoCloseable { return false; } - private PortResult getPortResult(ProducerState state) throws InterruptedException { + private PollState getPortResult(ProducerState state) throws InterruptedException { Future portResultFuture = state.completionService().poll(10, TimeUnit.MILLISECONDS); if (portResultFuture == null) { - return null; + return new PollState.Unavailable<>(); } - PortResult portResult = null; + PortResult portResult; try { portResult = portResultFuture.get(); + return new PollState.Success<>(portResult); } catch (ExecutionException e) { - // something more serious did not work - System.err.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage()); + Throwable cause = e.getCause(); + return new PollState.Failure<>(cause); } finally { state.activeWorkers().release(); state.inPipeline().decrementAndGet(); } - return portResult; } } -- cgit v1.3.1