| 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | // Copyright 2026 Egothor | |
| 3 | // Copyright 2026 Accenture | |
| 4 | package org.egothor.methodatlas; | |
| 5 | ||
| 6 | import java.util.Optional; | |
| 7 | ||
| 8 | /** | |
| 9 | * Thread-local holder for the current {@link ScanRun}, the JUL-friendly | |
| 10 | * equivalent of SLF4J's MDC. | |
| 11 | * | |
| 12 | * <p> | |
| 13 | * {@link MethodAtlasApp#run(String[], java.io.PrintWriter)} constructs a | |
| 14 | * {@link ScanRun} once at the top of every invocation and calls | |
| 15 | * {@link #set(ScanRun)} so that any code executed on the same thread for | |
| 16 | * the duration of the run can read the correlation id through | |
| 17 | * {@link #current()}. A custom {@code java.util.logging.Formatter} reads | |
| 18 | * the context and prepends the run id to every log record (introduced in | |
| 19 | * Item 20 of the architecture remediation plan). | |
| 20 | * </p> | |
| 21 | * | |
| 22 | * <h2>Thread safety</h2> | |
| 23 | * | |
| 24 | * <p> | |
| 25 | * Each thread sees its own value. The MethodAtlas CLI runs single-threaded | |
| 26 | * scans by default; AI provider calls are sequential. When concurrent | |
| 27 | * threads do appear (the {@link java.util.ServiceLoader} per-plugin | |
| 28 | * isolation), callers that want the run id on those threads must propagate | |
| 29 | * it explicitly. | |
| 30 | * </p> | |
| 31 | * | |
| 32 | * <h2>Cleanup</h2> | |
| 33 | * | |
| 34 | * <p> | |
| 35 | * Always pair {@link #set(ScanRun)} with a {@link #clear()} in a | |
| 36 | * {@code finally} block — without it, the thread-local reference outlives | |
| 37 | * the CLI invocation in container deployments that pool threads. The | |
| 38 | * standard MethodAtlas CLI exits the JVM at end-of-run, so cleanup matters | |
| 39 | * mainly when MethodAtlas is invoked programmatically. | |
| 40 | * </p> | |
| 41 | * | |
| 42 | * @since 1.0.0 | |
| 43 | */ | |
| 44 | public final class ScanRunContext { | |
| 45 | ||
| 46 | private static final ThreadLocal<ScanRun> CURRENT = new ThreadLocal<>(); | |
| 47 | ||
| 48 | private ScanRunContext() { | |
| 49 | // Utility class -- instantiation makes no sense. | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Sets the current scan run for the calling thread. | |
| 54 | * | |
| 55 | * @param run the run identifier; must not be {@code null} | |
| 56 | */ | |
| 57 | public static void set(ScanRun run) { | |
| 58 |
2
1. set : removed conditional - replaced equality check with false → KILLED 2. set : removed conditional - replaced equality check with true → KILLED |
if (run == null) { |
| 59 | throw new IllegalArgumentException("run must not be null; call clear() to remove the context"); | |
| 60 | } | |
| 61 |
1
1. set : removed call to java/lang/ThreadLocal::set → KILLED |
CURRENT.set(run); |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Returns the current scan run for the calling thread, or | |
| 66 | * {@link Optional#empty()} when no run is currently set (the standard | |
| 67 | * case before {@link MethodAtlasApp#main(String[])} runs or after | |
| 68 | * {@link #clear()}). | |
| 69 | * | |
| 70 | * @return optional carrying the current run | |
| 71 | */ | |
| 72 | public static Optional<ScanRun> current() { | |
| 73 |
1
1. current : replaced return value with Optional.empty for org/egothor/methodatlas/ScanRunContext::current → KILLED |
return Optional.ofNullable(CURRENT.get()); |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Removes the current scan run from the calling thread. Always called in | |
| 78 | * a {@code finally} block paired with {@link #set(ScanRun)} so that the | |
| 79 | * thread-local reference does not outlive the CLI invocation. | |
| 80 | */ | |
| 81 | public static void clear() { | |
| 82 |
1
1. clear : removed call to java/lang/ThreadLocal::remove → KILLED |
CURRENT.remove(); |
| 83 | } | |
| 84 | } | |
Mutations | ||
| 58 |
1.1 2.2 |
|
| 61 |
1.1 |
|
| 73 |
1.1 |
|
| 82 |
1.1 |