ReceiptFacade.java

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
Location : emit
Killed by : org.egothor.methodatlas.MethodAtlasAppReceiptTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppReceiptTest]/[method:defaultReceiptPath_isHonoured(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : emit
Killed by : org.egothor.methodatlas.MethodAtlasAppReceiptTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppReceiptTest]/[method:absentOverrideFile_omitsOverrideField(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

87

1.1
Location : emit
Killed by : org.egothor.methodatlas.MethodAtlasAppReceiptTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppReceiptTest]/[method:absentOverrideFile_omitsOverrideField(java.nio.file.Path)]
removed call to org/egothor/methodatlas/receipt/ReceiptWriter::write → KILLED

97

1.1
Location : mapper
Killed by : org.egothor.methodatlas.MethodAtlasAppReceiptTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppReceiptTest]/[method:absentOverrideFile_omitsOverrideField(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/receipt/ReceiptFacade::mapper → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1