| 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | // Copyright 2026 Egothor | |
| 3 | // Copyright 2026 Accenture | |
| 4 | package org.egothor.methodatlas.receipt; | |
| 5 | ||
| 6 | import java.io.IOException; | |
| 7 | import java.nio.file.Path; | |
| 8 | ||
| 9 | import com.fasterxml.jackson.annotation.JsonInclude; | |
| 10 | import tools.jackson.databind.ObjectMapper; | |
| 11 | import tools.jackson.databind.SerializationFeature; | |
| 12 | import tools.jackson.databind.json.JsonMapper; | |
| 13 | ||
| 14 | import org.egothor.methodatlas.CliConfig; | |
| 15 | ||
| 16 | /** | |
| 17 | * Single public entry point used by {@code MethodAtlasApp} to materialise a | |
| 18 | * reproducibility receipt. | |
| 19 | * | |
| 20 | * <p> | |
| 21 | * The facade exists because {@link ReceiptBuilder} and {@link ReceiptWriter} | |
| 22 | * are intentionally package-private — that keeps their helper methods, | |
| 23 | * canonical-form constants, and Jackson configuration off the public API | |
| 24 | * surface. The CLI orchestrator lives in a different package and would | |
| 25 | * otherwise need each of those classes promoted to {@code public}, which | |
| 26 | * would invite incidental external coupling. | |
| 27 | * </p> | |
| 28 | * | |
| 29 | * <p> | |
| 30 | * The shared {@link ObjectMapper} is cached on the facade so the CLI never | |
| 31 | * pays Jackson's first-call cost more than once per JVM invocation; reuse | |
| 32 | * across calls is safe because the mapper is configured idempotently. | |
| 33 | * </p> | |
| 34 | */ | |
| 35 | public final class ReceiptFacade { | |
| 36 | ||
| 37 | /** Default filename when {@code -receipt-file} is not supplied. */ | |
| 38 | private static final String DEFAULT_RECEIPT_FILENAME = "methodatlas-receipt.json"; | |
| 39 | ||
| 40 | private ReceiptFacade() { | |
| 41 | // Utility class. | |
| 42 | } | |
| 43 | ||
| 44 | /** | |
| 45 | * Initialisation-on-demand holder for the shared mapper. JVM class | |
| 46 | * initialisation semantics give thread-safe, race-free lazy creation | |
| 47 | * without {@code volatile} or {@code synchronized}. | |
| 48 | */ | |
| 49 | private static final class MapperHolder { | |
| 50 | /** | |
| 51 | * Shared, fully configured mapper instance; safe to reuse across calls. | |
| 52 | * It is built once with indented output and {@code NON_NULL} inclusion so | |
| 53 | * that callers never reconfigure it (which would not be thread-safe). | |
| 54 | */ | |
| 55 | /* default */ static final ObjectMapper INSTANCE = JsonMapper.builder() | |
| 56 | .enable(SerializationFeature.INDENT_OUTPUT) | |
| 57 | .changeDefaultPropertyInclusion( | |
| 58 | v -> JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) | |
| 59 | .build(); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Builds and writes a reproducibility receipt for the supplied scan | |
| 64 | * configuration. | |
| 65 | * | |
| 66 | * <p> | |
| 67 | * Resolves the output path from {@link CliConfig#receiptFile()} when set; | |
| 68 | * otherwise falls back to {@code methodatlas-receipt.json} in the current | |
| 69 | * working directory. The Jackson mapper is shared across calls; see the | |
| 70 | * class-level Javadoc for the rationale. | |
| 71 | * </p> | |
| 72 | * | |
| 73 | * @param config parsed CLI configuration; must not be {@code null} | |
| 74 | * @param toolVersion resolved tool version string ({@code "dev"} when the | |
| 75 | * JAR manifest has no implementation version) | |
| 76 | * @param outputModeName textual name of the chosen output mode (e.g. | |
| 77 | * {@code "SARIF"}); persisted in the receipt | |
| 78 | * @throws IOException if any input file cannot be read for hashing or the | |
| 79 | * receipt file cannot be written | |
| 80 | */ | |
| 81 | public static void emit(CliConfig config, String toolVersion, String outputModeName) | |
| 82 | throws IOException { | |
| 83 | ReproducibilityReceipt receipt = ReceiptBuilder.build(config, toolVersion, outputModeName); | |
| 84 |
2
1. emit : removed conditional - replaced equality check with true → KILLED 2. emit : removed conditional - replaced equality check with false → KILLED |
Path target = config.receiptFile() != null |
| 85 | ? config.receiptFile() | |
| 86 | : Path.of(DEFAULT_RECEIPT_FILENAME); | |
| 87 |
1
1. emit : removed call to org/egothor/methodatlas/receipt/ReceiptWriter::write → KILLED |
ReceiptWriter.write(receipt, mapper(), target); |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Returns the shared Jackson mapper, instantiated on first reference to | |
| 92 | * the inner {@link MapperHolder} class. | |
| 93 | * | |
| 94 | * @return shared {@link ObjectMapper}; never {@code null} | |
| 95 | */ | |
| 96 | private static ObjectMapper mapper() { | |
| 97 |
1
1. mapper : replaced return value with null for org/egothor/methodatlas/receipt/ReceiptFacade::mapper → KILLED |
return MapperHolder.INSTANCE; |
| 98 | } | |
| 99 | } | |
Mutations | ||
| 84 |
1.1 2.2 |
|
| 87 |
1.1 |
|
| 97 |
1.1 |