| 1 | /* | |
| 2 | * Copyright 2014-2026 Grzegorz Piwowarek, https://4comprehension.com/ | |
| 3 | * | |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | * you may not use this file except in compliance with the License. | |
| 6 | * You may obtain a copy of the License at | |
| 7 | * | |
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | * | |
| 10 | * Unless required by applicable law or agreed to in writing, software | |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | * See the License for the specific language governing permissions and | |
| 14 | * limitations under the License. | |
| 15 | */ | |
| 16 | package com.pivovarit.collectors; | |
| 17 | ||
| 18 | import java.time.Duration; | |
| 19 | import java.util.List; | |
| 20 | import java.util.concurrent.Executor; | |
| 21 | import java.util.concurrent.TimeUnit; | |
| 22 | import java.util.function.UnaryOperator; | |
| 23 | ||
| 24 | /** | |
| 25 | * Fluent configuration builder for collectors that <em>stream</em> results. | |
| 26 | * <p> | |
| 27 | * Instances of this class are used internally to accumulate configuration options that are later | |
| 28 | * interpreted by {@link ConfigProcessor}. | |
| 29 | */ | |
| 30 | public final class StreamingConfigurer { | |
| 31 | ||
| 32 | private final ConfigurerState state = new ConfigurerState(); | |
| 33 | ||
| 34 | StreamingConfigurer() { | |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Preserves encounter order of the input when emitting results. | |
| 39 | * <p> | |
| 40 | * Enabling this option may reduce throughput compared to unordered streaming, since it can | |
| 41 | * require additional coordination and buffering to emit results in encounter order. | |
| 42 | * | |
| 43 | * @return this configurer instance for fluent chaining | |
| 44 | */ | |
| 45 | public StreamingConfigurer ordered() { | |
| 46 |
1
1. ordered : removed call to com/pivovarit/collectors/ConfigurerState::ordered → KILLED |
state.ordered(); |
| 47 |
1
1. ordered : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::ordered → KILLED |
return this; |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Enables batching of work submitted to workers. | |
| 52 | * <p> | |
| 53 | * When enabled, each worker thread receives a batch of input items and processes them in one go, | |
| 54 | * instead of scheduling one task per item. This reduces the number of tasks created and typically | |
| 55 | * decreases contention on the underlying worker queue. | |
| 56 | * | |
| 57 | * <p><b>Note:</b> Depending on batch sizing and workload skew, batching may reduce load balancing and | |
| 58 | * can lead to thread starvation (some workers become idle while others remain overloaded). | |
| 59 | * | |
| 60 | * @return this configurer instance for fluent chaining | |
| 61 | */ | |
| 62 | public StreamingConfigurer batching() { | |
| 63 |
1
1. batching : removed call to com/pivovarit/collectors/ConfigurerState::batching → KILLED |
state.batching(); |
| 64 |
1
1. batching : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::batching → KILLED |
return this; |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Sets the maximum level of parallelism. | |
| 69 | * <p> | |
| 70 | * This limits the number of tasks submitted to the worker queue at once, effectively bounding | |
| 71 | * the amount of in-flight work and the maximum concurrency used by the collector. | |
| 72 | * | |
| 73 | * <p><b>Note:</b> {@code parallelism(1)} streaming runs the mapper <em>synchronously on the calling | |
| 74 | * thread</em> as elements are accumulated, rather than dispatching work to an executor. As a | |
| 75 | * consequence, any {@link #executor(Executor)}, {@link #executorDecorator(UnaryOperator)} or | |
| 76 | * {@link #taskDecorator(UnaryOperator)} configured alongside {@code parallelism(1)} is ignored. | |
| 77 | * This is asymmetric with the collecting collectors (e.g. {@code parallel(...)}), where | |
| 78 | * {@code parallelism(1)} still executes on the configured executor — keep this in mind for | |
| 79 | * thread-isolation or context-propagation (MDC, OpenTelemetry, {@code SecurityContext}) use cases, | |
| 80 | * which require {@code parallelism(2)} or higher when streaming. | |
| 81 | * | |
| 82 | * @param parallelism the desired parallelism level (must be positive) | |
| 83 | * | |
| 84 | * @return this configurer instance for fluent chaining | |
| 85 | */ | |
| 86 | public StreamingConfigurer parallelism(int parallelism) { | |
| 87 |
1
1. parallelism : removed call to com/pivovarit/collectors/ConfigurerState::parallelism → KILLED |
state.parallelism(parallelism); |
| 88 |
1
1. parallelism : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::parallelism → KILLED |
return this; |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Sets a total-operation timeout for streaming results. | |
| 93 | * <p> | |
| 94 | * The timeout budget starts on the first pull from the returned stream. If the next result is not | |
| 95 | * available within the remaining budget, stream traversal throws a | |
| 96 | * {@link java.util.concurrent.CompletionException} wrapping a | |
| 97 | * {@link java.util.concurrent.TimeoutException}. | |
| 98 | * | |
| 99 | * <p><b>Note:</b> When the timeout trips, stream traversal stops with an exception, but any | |
| 100 | * in-flight tasks are <em>not</em> cancelled and keep running to completion in the background. | |
| 101 | * | |
| 102 | * <p><b>Note:</b> The timeout is ignored at {@code parallelism(1)}, which runs the mapper | |
| 103 | * synchronously on the calling thread and therefore cannot be interrupted by a timeout — see | |
| 104 | * {@link #parallelism(int)}. | |
| 105 | * | |
| 106 | * @param duration the timeout duration (must be positive) | |
| 107 | * @param unit the time unit of the {@code duration} argument | |
| 108 | * | |
| 109 | * @return this configurer instance for fluent chaining | |
| 110 | */ | |
| 111 | public StreamingConfigurer timeout(long duration, TimeUnit unit) { | |
| 112 |
1
1. timeout : removed call to com/pivovarit/collectors/ConfigurerState::timeout → KILLED |
state.timeout(duration, unit); |
| 113 |
1
1. timeout : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::timeout → SURVIVED |
return this; |
| 114 | } | |
| 115 | ||
| 116 | /** | |
| 117 | * Sets a total-operation timeout for streaming results. | |
| 118 | * <p> | |
| 119 | * The timeout budget starts on the first pull from the returned stream. If the next result is not | |
| 120 | * available within the remaining budget, stream traversal throws a | |
| 121 | * {@link java.util.concurrent.CompletionException} wrapping a | |
| 122 | * {@link java.util.concurrent.TimeoutException}. | |
| 123 | * | |
| 124 | * <p><b>Note:</b> When the timeout trips, stream traversal stops with an exception, but any | |
| 125 | * in-flight tasks are <em>not</em> cancelled and keep running to completion in the background. | |
| 126 | * | |
| 127 | * <p><b>Note:</b> The timeout is ignored at {@code parallelism(1)}, which runs the mapper | |
| 128 | * synchronously on the calling thread and therefore cannot be interrupted by a timeout — see | |
| 129 | * {@link #parallelism(int)}. | |
| 130 | * | |
| 131 | * @param duration the timeout duration (must be positive) | |
| 132 | * | |
| 133 | * @return this configurer instance for fluent chaining | |
| 134 | */ | |
| 135 | public StreamingConfigurer timeout(Duration duration) { | |
| 136 |
1
1. timeout : removed call to com/pivovarit/collectors/ConfigurerState::timeout → KILLED |
state.timeout(duration); |
| 137 |
1
1. timeout : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::timeout → SURVIVED |
return this; |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Sets the {@link Executor} used for running tasks. | |
| 142 | * <p> | |
| 143 | * Use this to supply a custom execution strategy (for example, a dedicated thread pool). | |
| 144 | * | |
| 145 | * <p><b>Note:</b> The provided executor must not <em>drop</em> tasks on rejection (e.g. using a | |
| 146 | * {@code RejectedExecutionHandler} that discards submitted work). Dropping tasks will cause the | |
| 147 | * stream to wait for results that will never be produced, which can lead to deadlocks. | |
| 148 | * | |
| 149 | * <p><b>Note:</b> The executor is ignored at {@code parallelism(1)}, which runs synchronously on the | |
| 150 | * calling thread — see {@link #parallelism(int)}. | |
| 151 | * | |
| 152 | * @param executor the executor to use | |
| 153 | * | |
| 154 | * @return this configurer instance for fluent chaining | |
| 155 | */ | |
| 156 | public StreamingConfigurer executor(Executor executor) { | |
| 157 |
1
1. executor : removed call to com/pivovarit/collectors/ConfigurerState::executor → KILLED |
state.executor(executor); |
| 158 |
1
1. executor : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::executor → KILLED |
return this; |
| 159 | } | |
| 160 | ||
| 161 | /** | |
| 162 | * Decorates the executor used for running tasks. | |
| 163 | * <p> | |
| 164 | * The decorator receives the resolved executor (either the default virtual-thread executor or | |
| 165 | * the one provided via {@link #executor(Executor)}) and returns a wrapped replacement. | |
| 166 | * This is useful for augmenting the executor with cross-cutting concerns such as context | |
| 167 | * propagation (MDC, OpenTelemetry spans, etc.) or monitoring, without replacing the executor entirely. | |
| 168 | * | |
| 169 | * <p><b>Note:</b> The executor returned by the decorator must not <em>drop</em> tasks on rejection. | |
| 170 | * Dropping tasks will cause the stream to wait for results that will never be produced, | |
| 171 | * which can lead to deadlocks. | |
| 172 | * | |
| 173 | * <p><b>Note:</b> The decorator is ignored at {@code parallelism(1)}, which runs synchronously on the | |
| 174 | * calling thread — see {@link #parallelism(int)}. | |
| 175 | * | |
| 176 | * @param decorator a function that wraps the resolved executor | |
| 177 | * | |
| 178 | * @return this configurer instance for fluent chaining | |
| 179 | */ | |
| 180 | public StreamingConfigurer executorDecorator(UnaryOperator<Executor> decorator) { | |
| 181 |
1
1. executorDecorator : removed call to com/pivovarit/collectors/ConfigurerState::executorDecorator → KILLED |
state.executorDecorator(decorator); |
| 182 |
1
1. executorDecorator : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::executorDecorator → SURVIVED |
return this; |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * Decorates each individual task before it is submitted to the executor. | |
| 187 | * <p> | |
| 188 | * The decorator receives the {@link Runnable} representing a single unit of work and returns a | |
| 189 | * wrapped replacement that runs in its place. This is useful for propagating thread-local context | |
| 190 | * (e.g. MDC entries, OpenTelemetry spans, {@code SecurityContext}) into worker threads, or for | |
| 191 | * per-task instrumentation, without replacing the executor entirely. | |
| 192 | * | |
| 193 | * <p>Unlike {@link #executorDecorator(UnaryOperator)}, which wraps the executor as a whole, | |
| 194 | * this decorator is applied to each task individually and runs on the worker thread. | |
| 195 | * | |
| 196 | * <p><b>Note:</b> The decorator is ignored at {@code parallelism(1)}, which runs synchronously on the | |
| 197 | * calling thread — see {@link #parallelism(int)}. | |
| 198 | * | |
| 199 | * @param decorator a function that wraps each submitted task | |
| 200 | * | |
| 201 | * @return this configurer instance for fluent chaining | |
| 202 | */ | |
| 203 | public StreamingConfigurer taskDecorator(UnaryOperator<Runnable> decorator) { | |
| 204 |
1
1. taskDecorator : removed call to com/pivovarit/collectors/ConfigurerState::taskDecorator → KILLED |
state.taskDecorator(decorator); |
| 205 |
1
1. taskDecorator : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::taskDecorator → SURVIVED |
return this; |
| 206 | } | |
| 207 | ||
| 208 | List<ConfigProcessor.Option> getConfig() { | |
| 209 |
1
1. getConfig : replaced return value with Collections.emptyList for com/pivovarit/collectors/StreamingConfigurer::getConfig → KILLED |
return state.getConfig(); |
| 210 | } | |
| 211 | ||
| 212 | void validate() { | |
| 213 |
1
1. validate : removed call to com/pivovarit/collectors/ConfigurerState::validate → KILLED |
state.validate(); |
| 214 | } | |
| 215 | } | |
Mutations | ||
| 46 |
1.1 |
|
| 47 |
1.1 |
|
| 63 |
1.1 |
|
| 64 |
1.1 |
|
| 87 |
1.1 |
|
| 88 |
1.1 |
|
| 112 |
1.1 |
|
| 113 |
1.1 |
|
| 136 |
1.1 |
|
| 137 |
1.1 |
|
| 157 |
1.1 |
|
| 158 |
1.1 |
|
| 181 |
1.1 |
|
| 182 |
1.1 |
|
| 204 |
1.1 |
|
| 205 |
1.1 |
|
| 209 |
1.1 |
|
| 213 |
1.1 |