ApplyTagsCommand.java

1
package org.egothor.methodatlas.command;
2
3
import java.io.IOException;
4
import java.io.PrintWriter;
5
import java.nio.file.Path;
6
import java.nio.file.Paths;
7
import java.util.LinkedHashMap;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.logging.Level;
11
import java.util.logging.Logger;
12
import java.util.stream.Collectors;
13
14
import org.egothor.methodatlas.AiResultCache;
15
import org.egothor.methodatlas.ClassificationOverride;
16
import org.egothor.methodatlas.CliConfig;
17
import org.egothor.methodatlas.ai.AiSuggestionEngine;
18
import org.egothor.methodatlas.api.DiscoveredMethod;
19
import org.egothor.methodatlas.api.SourcePatcher;
20
import org.egothor.methodatlas.api.TestDiscovery;
21
import org.egothor.methodatlas.api.TestDiscoveryConfig;
22
23
/**
24
 * CLI command handler for the {@code -apply-tags} mode.
25
 *
26
 * <p>
27
 * Discovers test methods via the configured {@link TestDiscovery} providers,
28
 * resolves AI suggestions for each class, and delegates the actual source file
29
 * write-back to the matching {@link SourcePatcher} implementation. A summary
30
 * line is written to the supplied writer on completion.
31
 * </p>
32
 *
33
 * @see ApplyTagsFromCsvCommand
34
 * @see org.egothor.methodatlas.api.SourcePatcher
35
 */
36
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
37
public final class ApplyTagsCommand implements Command {
38
39
    private static final Logger LOG = Logger.getLogger(ApplyTagsCommand.class.getName());
40
41
    private final CliConfig cliConfig;
42
    private final TestDiscoveryConfig discoveryConfig;
43
    private final AiSuggestionEngine aiEngine;
44
    private final ClassificationOverride override;
45
    private final AiResultCache aiCache;
46
47
    /**
48
     * Creates a new apply-tags command.
49
     *
50
     * @param cliConfig       full parsed CLI configuration
51
     * @param discoveryConfig discovery configuration forwarded to providers
52
     * @param aiEngine        AI engine providing suggestions; {@code null} when
53
     *                        AI is disabled
54
     * @param override        human classification overrides
55
     * @param aiCache         AI result cache
56
     */
57
    public ApplyTagsCommand(CliConfig cliConfig, TestDiscoveryConfig discoveryConfig,
58
            AiSuggestionEngine aiEngine, ClassificationOverride override,
59
            AiResultCache aiCache) {
60
        this.cliConfig = cliConfig;
61
        this.discoveryConfig = discoveryConfig;
62
        this.aiEngine = aiEngine;
63
        this.override = override;
64
        this.aiCache = aiCache;
65
    }
66
67
    /**
68
     * Discovers test methods, gathers AI suggestions, and patches source files.
69
     *
70
     * @param out writer for the completion summary line
71
     * @return {@code 0} if all files were processed successfully, {@code 1}
72
     *         if any file produced a parse or processing error
73
     * @throws IOException if traversing a file tree fails
74
     */
75
    @Override
76
    public int execute(PrintWriter out) throws IOException {
77 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
        List<Path> roots = cliConfig.paths().isEmpty() ? List.of(Paths.get(".")) : cliConfig.paths();
78
        List<SourcePatcher> patchers = CommandSupport.loadPatchers(discoveryConfig);
79
        List<TestDiscovery> providers = CommandSupport.loadProviders(discoveryConfig);
80
81
        // Initializers are omitted: both variables are assigned unconditionally in the
82
        // try block, which satisfies JLS §16.2.15 definite-assignment for try-finally
83
        // without a catch clause.
84
        Map<Path, List<DiscoveredMethod>> byFile;
85
        boolean hadErrors;
86
        try {
87
            byFile = CommandSupport.collectMethodsByFile(roots, providers);
88
            hadErrors = providers.stream().anyMatch(TestDiscovery::hadErrors);
89
        } finally {
90 1 1. execute : removed call to org/egothor/methodatlas/command/CommandSupport::closeAll → SURVIVED
            CommandSupport.closeAll(providers);
91
        }
92
93
        CommandSupport.AiRuntime ai =
94
                new CommandSupport.AiRuntime(cliConfig.aiOptions(), aiEngine, override, aiCache);
95
96
        int modifiedFiles = 0;
97
        int totalAnnotations = 0;
98
99
        for (Map.Entry<Path, List<DiscoveredMethod>> entry : byFile.entrySet()) {
100
            Path sourceFile = entry.getKey();
101
            List<DiscoveredMethod> methods = entry.getValue();
102
103
            SourcePatcher patcher = patchers.stream()
104 2 1. lambda$execute$0 : replaced boolean return with true for org/egothor/methodatlas/command/ApplyTagsCommand::lambda$execute$0 → SURVIVED
2. lambda$execute$0 : replaced boolean return with false for org/egothor/methodatlas/command/ApplyTagsCommand::lambda$execute$0 → KILLED
                    .filter(p -> p.supports(sourceFile))
105
                    .findFirst().orElse(null);
106 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
            if (patcher == null) {
107
                continue;
108
            }
109
110
            Map<String, List<DiscoveredMethod>> byClass = methods.stream()
111
                    .collect(Collectors.groupingBy(DiscoveredMethod::fqcn,
112
                            LinkedHashMap::new, Collectors.toList()));
113
114
            Map<String, List<String>> tagsToApply = new LinkedHashMap<>();
115
            Map<String, String> displayNames = new LinkedHashMap<>();
116
117 1 1. execute : removed call to org/egothor/methodatlas/command/CommandSupport::gatherAiSuggestionsForFile → KILLED
            CommandSupport.gatherAiSuggestionsForFile(byClass, ai, aiCache, tagsToApply, displayNames);
118
119 4 1. execute : removed conditional - replaced equality check with true → SURVIVED
2. execute : removed conditional - replaced equality check with false → SURVIVED
3. execute : removed conditional - replaced equality check with true → KILLED
4. execute : removed conditional - replaced equality check with false → KILLED
            if (!tagsToApply.isEmpty() || !displayNames.isEmpty()) {
120
                try {
121
                    int changes = patcher.patch(sourceFile, tagsToApply, displayNames, out);
122 3 1. execute : removed conditional - replaced comparison check with true → SURVIVED
2. execute : changed conditional boundary → SURVIVED
3. execute : removed conditional - replaced comparison check with false → KILLED
                    if (changes > 0) {
123 1 1. execute : Changed increment from 1 to -1 → KILLED
                        modifiedFiles++;
124 1 1. execute : Replaced integer addition with subtraction → SURVIVED
                        totalAnnotations += changes;
125
                    }
126
                } catch (IOException e) {
127
                    if (LOG.isLoggable(Level.WARNING)) {
128
                        LOG.log(Level.WARNING, "Cannot process: " + sourceFile, e);
129
                    }
130
                    hadErrors = true;
131
                }
132
            }
133
        }
134
135 1 1. execute : removed call to java/io/PrintWriter::println → KILLED
        out.println("Apply-tags complete: " + totalAnnotations + " annotation(s) added to "
136
                + modifiedFiles + " file(s)");
137 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
        return hadErrors ? 1 : 0;
138
    }
139
}

Mutations

77

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_addsDisplayNameImport(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

90

1.1
Location : execute
Killed by : none
removed call to org/egothor/methodatlas/command/CommandSupport::closeAll → SURVIVED
Covering tests

104

1.1
Location : lambda$execute$0
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_addsDisplayNameImport(java.nio.file.Path)]
replaced boolean return with false for org/egothor/methodatlas/command/ApplyTagsCommand::lambda$execute$0 → KILLED

2.2
Location : lambda$execute$0
Killed by : none
replaced boolean return with true for org/egothor/methodatlas/command/ApplyTagsCommand::lambda$execute$0 → SURVIVED
Covering tests

106

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_addsDisplayNameImport(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

117

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_addsDisplayNameImport(java.nio.file.Path)]
removed call to org/egothor/methodatlas/command/CommandSupport::gatherAiSuggestionsForFile → KILLED

119

1.1
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

3.3
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_addsTagImport(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

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

122

1.1
Location : execute
Killed by : none
removed conditional - replaced comparison check with true → SURVIVED
Covering tests

2.2
Location : execute
Killed by : none
changed conditional boundary → SURVIVED Covering tests

3.3
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_summaryCountsMatchAnnotationsAdded(java.nio.file.Path)]
removed conditional - replaced comparison check with false → KILLED

123

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_summaryCountsMatchAnnotationsAdded(java.nio.file.Path)]
Changed increment from 1 to -1 → KILLED

124

1.1
Location : execute
Killed by : none
Replaced integer addition with subtraction → SURVIVED
Covering tests

135

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsTest]/[method:applyTags_summaryLineAlwaysEmitted(java.nio.file.Path)]
removed call to java/io/PrintWriter::println → KILLED

137

1.1
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

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

Active mutators

Tests examined


Report generated by PIT 1.22.1