| 1 | package org.egothor.methodatlas; | |
| 2 | ||
| 3 | import java.io.IOException; | |
| 4 | import java.io.PrintWriter; | |
| 5 | import java.nio.file.Files; | |
| 6 | import java.nio.file.Path; | |
| 7 | import java.nio.file.Paths; | |
| 8 | import java.util.ArrayList; | |
| 9 | import java.util.HashMap; | |
| 10 | import java.util.LinkedHashMap; | |
| 11 | import java.util.LinkedHashSet; | |
| 12 | import java.util.List; | |
| 13 | import java.util.Map; | |
| 14 | import java.util.Set; | |
| 15 | import java.util.logging.Level; | |
| 16 | import java.util.logging.Logger; | |
| 17 | import java.util.stream.Stream; | |
| 18 | ||
| 19 | import org.egothor.methodatlas.api.ScanRecord; | |
| 20 | import org.egothor.methodatlas.emit.DeltaReport; | |
| 21 | import org.egothor.methodatlas.api.SourcePatcher; | |
| 22 | ||
| 23 | /** | |
| 24 | * Engine that applies annotation changes to source files driven by a | |
| 25 | * reviewed MethodAtlas CSV export. | |
| 26 | * | |
| 27 | * <p> | |
| 28 | * The {@code -apply-tags-from-csv} workflow allows a security or development | |
| 29 | * team to review MethodAtlas output in a CSV file, adjust the {@code tags} and | |
| 30 | * {@code display_name} columns to reflect the desired annotation state, and | |
| 31 | * then replay those decisions back into the source code. The CSV acts as a | |
| 32 | * <em>desired-state specification</em>: after a successful run, re-running | |
| 33 | * MethodAtlas on the same source tree would reproduce an equivalent CSV. | |
| 34 | * </p> | |
| 35 | * | |
| 36 | * <h2>Invariant</h2> | |
| 37 | * <p> | |
| 38 | * The CSV must be complete — it must contain one row for every test method | |
| 39 | * currently present in the scanned source tree (matching the configured file | |
| 40 | * suffixes and test annotations). A method that appears in the source but not | |
| 41 | * in the CSV, or in the CSV but not in the source, constitutes a | |
| 42 | * <em>mismatch</em> and is reported as a warning. When a mismatch limit is | |
| 43 | * configured, the engine aborts without making any source changes if the | |
| 44 | * number of mismatches reaches or exceeds that limit. | |
| 45 | * </p> | |
| 46 | * | |
| 47 | * <h2>What is applied</h2> | |
| 48 | * <ul> | |
| 49 | * <li>{@code tags} column — the exact set of {@code @Tag} annotations desired | |
| 50 | * on the method; existing tags not in the list are removed, missing tags | |
| 51 | * are added</li> | |
| 52 | * <li>{@code display_name} column — the desired {@code @DisplayName} text; | |
| 53 | * empty means "remove any existing {@code @DisplayName}"</li> | |
| 54 | * </ul> | |
| 55 | * | |
| 56 | * <h2>AI-column promotion (risky, opt-in)</h2> | |
| 57 | * <p> | |
| 58 | * <strong>Not recommended.</strong> When the caller passes {@code promoteAi}, | |
| 59 | * the engine falls back to the AI-suggested {@code ai_tags} / | |
| 60 | * {@code ai_display_name} columns for any method whose curated {@code tags} / | |
| 61 | * {@code display_name} column is blank, and writes that raw AI suggestion into | |
| 62 | * source. Promotion is per-field and independent, and only fires when the | |
| 63 | * curated value is blank <em>and</em> a non-blank AI value is present. | |
| 64 | * </p> | |
| 65 | * <p> | |
| 66 | * This mode deliberately bypasses the human review step that the | |
| 67 | * apply-from-csv workflow exists to enforce — AI output reaches source without | |
| 68 | * validation. It should not be used unless the promotion has been rethought and | |
| 69 | * approved for the target environment. Every promoted value is counted and | |
| 70 | * reported on the completion summary line (and itemised under {@code -verbose}) | |
| 71 | * so the run log records exactly which source writes originated from AI rather | |
| 72 | * than from a reviewed column. | |
| 73 | * </p> | |
| 74 | * | |
| 75 | * <h2>Mismatch handling</h2> | |
| 76 | * <p> | |
| 77 | * If {@code mismatchLimit} is {@code -1} (no limit), mismatches are logged as | |
| 78 | * warnings and the engine proceeds. If {@code mismatchLimit} is {@code >= 0}, | |
| 79 | * the engine first counts mismatches and aborts with exit code {@code 1} when | |
| 80 | * the count is {@code >= mismatchLimit}. Setting the limit to {@code 1} | |
| 81 | * therefore causes any mismatch to abort the run without touching source files. | |
| 82 | * </p> | |
| 83 | * | |
| 84 | * <h2>Languages supported for write-back</h2> | |
| 85 | * <p> | |
| 86 | * Source write-back is only available for languages whose discovery plugin | |
| 87 | * ships a {@link SourcePatcher} implementation — currently | |
| 88 | * <strong>Java</strong> (JUnit 5 / 4 / TestNG) and <strong>C#</strong> | |
| 89 | * (xUnit / NUnit / MSTest). Files whose language has no patcher are skipped | |
| 90 | * during the apply phase; a per-file notice is written to {@code log} and | |
| 91 | * the aggregate skip count is appended to the completion summary line. | |
| 92 | * These files do not appear in the source-method index built by | |
| 93 | * {@link SourcePatcher#discoverMethodsByClass(Path)} either, so rows in the | |
| 94 | * CSV that describe tests in unsupported languages will be reported as | |
| 95 | * mismatches. | |
| 96 | * </p> | |
| 97 | * | |
| 98 | * <p> | |
| 99 | * This class is a non-instantiable utility holder. | |
| 100 | * </p> | |
| 101 | * | |
| 102 | * @see SourcePatcher | |
| 103 | * @see MethodAtlasApp | |
| 104 | */ | |
| 105 | public final class ApplyTagsFromCsvEngine { | |
| 106 | ||
| 107 | private static final Logger LOG = Logger.getLogger(ApplyTagsFromCsvEngine.class.getName()); | |
| 108 | ||
| 109 | private ApplyTagsFromCsvEngine() { | |
| 110 | } | |
| 111 | ||
| 112 | /** | |
| 113 | * Applies annotation changes from a reviewed CSV to source files. | |
| 114 | * | |
| 115 | * <p> | |
| 116 | * Source-method inventory (for mismatch detection) and source file write-back | |
| 117 | * are both delegated to the supplied {@link SourcePatcher} implementations via | |
| 118 | * {@link SourcePatcher#discoverMethodsByClass(java.nio.file.Path)} and | |
| 119 | * {@link SourcePatcher#patch(java.nio.file.Path, java.util.Map, java.util.Map, | |
| 120 | * java.io.PrintWriter)} respectively. | |
| 121 | * </p> | |
| 122 | * | |
| 123 | * @param csvFile path to a MethodAtlas CSV produced with a previous | |
| 124 | * scan and reviewed by the team; must contain | |
| 125 | * {@code fqcn}, {@code method}, {@code tags}, and | |
| 126 | * {@code display_name} columns | |
| 127 | * @param roots source root directories to scan for test files | |
| 128 | * @param mismatchLimit maximum number of mismatches before aborting; | |
| 129 | * {@code -1} means no limit (warn and proceed) | |
| 130 | * @param patchers list of configured {@link SourcePatcher} implementations | |
| 131 | * @param log writer for progress and summary output | |
| 132 | * @param verbose when {@code true}, print the CSV desired-state keys, | |
| 133 | * the keys discovered in the source tree, and the | |
| 134 | * key-by-key match result to {@code log}, so a run that | |
| 135 | * reports zero updates can be diagnosed (for example a | |
| 136 | * fully qualified class name or working-directory | |
| 137 | * mismatch between the CSV and the scanned source) | |
| 138 | * @param promoteAi <strong>risky, not recommended</strong>: when | |
| 139 | * {@code true}, the engine falls back to the | |
| 140 | * {@code ai_tags} / {@code ai_display_name} columns for | |
| 141 | * any method whose curated {@code tags} / | |
| 142 | * {@code display_name} column is blank, writing the raw | |
| 143 | * AI suggestion into source without human review; | |
| 144 | * every promoted value is counted on the summary line | |
| 145 | * and itemised under {@code verbose} | |
| 146 | * @return {@code 0} on success, {@code 1} when the mismatch limit is | |
| 147 | * exceeded or a fatal error occurs | |
| 148 | * @throws IOException if the CSV file or source files cannot be read or | |
| 149 | * written | |
| 150 | */ | |
| 151 | public static int apply(Path csvFile, List<Path> roots, | |
| 152 | int mismatchLimit, List<SourcePatcher> patchers, PrintWriter log, boolean verbose, | |
| 153 | boolean promoteAi) | |
| 154 | throws IOException { | |
| 155 | ||
| 156 | // ── Step 1: load the desired-state CSV ──────────────────────────────── | |
| 157 | List<ScanRecord> records = DeltaReport.loadRecords(csvFile); | |
| 158 |
2
1. apply : removed conditional - replaced equality check with false → KILLED 2. apply : removed conditional - replaced equality check with true → KILLED |
if (records.isEmpty()) { |
| 159 |
1
1. apply : removed call to java/io/PrintWriter::println → KILLED |
log.println("Apply-tags-from-csv: CSV file contains no records — nothing to apply."); |
| 160 | return 0; | |
| 161 | } | |
| 162 | ||
| 163 |
1
1. apply : Replaced integer multiplication with division → SURVIVED |
Map<String, ScanRecord> desiredState = new HashMap<>(records.size() * 2); |
| 164 | for (ScanRecord r : records) { | |
| 165 | desiredState.put(key(r.fqcn(), r.method()), r); | |
| 166 | } | |
| 167 | ||
| 168 |
2
1. apply : removed conditional - replaced equality check with true → SURVIVED 2. apply : removed conditional - replaced equality check with false → KILLED |
if (verbose) { |
| 169 |
1
1. apply : removed call to org/egothor/methodatlas/ApplyTagsFromCsvEngine::logVerboseHeader → KILLED |
logVerboseHeader(log, csvFile, roots, desiredState); |
| 170 |
2
1. apply : removed conditional - replaced equality check with true → SURVIVED 2. apply : removed conditional - replaced equality check with false → SURVIVED |
if (promoteAi) { |
| 171 |
1
1. apply : removed call to java/io/PrintWriter::println → NO_COVERAGE |
log.println("[verbose] -promote-ai: ENABLED — blank tags/display_name " |
| 172 | + "will be filled from ai_tags/ai_display_name (unvalidated AI output)"); | |
| 173 | } | |
| 174 | } | |
| 175 | ||
| 176 | // ── Step 2: scan source to build current method inventory ───────────── | |
| 177 | // Build a map from source file path → list of (fqcn, methodName) pairs | |
| 178 | // by asking each SourcePatcher to discover methods in supported files. | |
| 179 | Map<Path, List<MethodKey>> sourceIndex = | |
| 180 | buildSourceIndex(roots, patchers); | |
| 181 | ||
| 182 | Set<String> sourceKeys = new LinkedHashSet<>(); | |
| 183 | for (List<MethodKey> methods : sourceIndex.values()) { | |
| 184 | for (MethodKey mk : methods) { | |
| 185 | sourceKeys.add(key(mk.fqcn(), mk.method())); | |
| 186 | } | |
| 187 | } | |
| 188 | ||
| 189 |
2
1. apply : removed conditional - replaced equality check with true → SURVIVED 2. apply : removed conditional - replaced equality check with false → KILLED |
if (verbose) { |
| 190 |
1
1. apply : removed call to org/egothor/methodatlas/ApplyTagsFromCsvEngine::logVerboseSource → KILLED |
logVerboseSource(log, sourceIndex, sourceKeys); |
| 191 | } | |
| 192 | ||
| 193 | // ── Step 3: compute mismatches (symmetric difference) ───────────────── | |
| 194 | Set<String> inCsvNotSource = new LinkedHashSet<>(desiredState.keySet()); | |
| 195 | inCsvNotSource.removeAll(sourceKeys); | |
| 196 | ||
| 197 | Set<String> inSourceNotCsv = new LinkedHashSet<>(sourceKeys); | |
| 198 | inSourceNotCsv.removeAll(desiredState.keySet()); | |
| 199 | ||
| 200 |
1
1. apply : Replaced integer addition with subtraction → KILLED |
int mismatchCount = inCsvNotSource.size() + inSourceNotCsv.size(); |
| 201 | ||
| 202 |
2
1. apply : removed conditional - replaced equality check with true → SURVIVED 2. apply : removed conditional - replaced equality check with false → KILLED |
if (verbose) { |
| 203 |
1
1. apply : removed call to org/egothor/methodatlas/ApplyTagsFromCsvEngine::logVerboseMatch → KILLED |
logVerboseMatch(log, desiredState.size(), inCsvNotSource, inSourceNotCsv); |
| 204 | } | |
| 205 | ||
| 206 | // ── Step 4: enforce mismatch limit ──────────────────────────────────── | |
| 207 |
6
1. apply : changed conditional boundary → SURVIVED 2. apply : removed conditional - replaced comparison check with false → KILLED 3. apply : removed conditional - replaced comparison check with true → KILLED 4. apply : changed conditional boundary → KILLED 5. apply : removed conditional - replaced comparison check with true → KILLED 6. apply : removed conditional - replaced comparison check with false → KILLED |
if (mismatchLimit >= 0 && mismatchCount >= mismatchLimit) { |
| 208 |
1
1. apply : replaced int return with 0 for org/egothor/methodatlas/ApplyTagsFromCsvEngine::apply → KILLED |
return reportMismatchesAndAbort(inCsvNotSource, inSourceNotCsv, mismatchCount, mismatchLimit, log); |
| 209 | } | |
| 210 | ||
| 211 | // ── Step 5: warn about mismatches (no limit, or below limit) ────────── | |
| 212 |
1
1. apply : removed call to org/egothor/methodatlas/ApplyTagsFromCsvEngine::warnMismatches → SURVIVED |
warnMismatches(inCsvNotSource, inSourceNotCsv); |
| 213 | ||
| 214 | // ── Step 6: apply changes file by file ──────────────────────────────── | |
| 215 |
1
1. apply : replaced int return with 0 for org/egothor/methodatlas/ApplyTagsFromCsvEngine::apply → SURVIVED |
return applyFilesLoop(sourceIndex, desiredState, patchers, mismatchCount, log, verbose, promoteAi); |
| 216 | } | |
| 217 | ||
| 218 | private static int reportMismatchesAndAbort(Set<String> inCsvNotSource, Set<String> inSourceNotCsv, | |
| 219 | int mismatchCount, int mismatchLimit, PrintWriter log) { | |
| 220 | for (String k : inCsvNotSource) { | |
| 221 |
1
1. reportMismatchesAndAbort : removed call to java/io/PrintWriter::println → KILLED |
log.println("MISMATCH (in CSV, not in source): " + k); |
| 222 | } | |
| 223 | for (String k : inSourceNotCsv) { | |
| 224 |
1
1. reportMismatchesAndAbort : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("MISMATCH (in source, not in CSV): " + k); |
| 225 | } | |
| 226 |
1
1. reportMismatchesAndAbort : removed call to java/io/PrintWriter::println → KILLED |
log.println("Apply-tags-from-csv aborted: " + mismatchCount |
| 227 | + " mismatch(es) >= limit " + mismatchLimit + ". No source files were modified."); | |
| 228 |
1
1. reportMismatchesAndAbort : replaced int return with 0 for org/egothor/methodatlas/ApplyTagsFromCsvEngine::reportMismatchesAndAbort → KILLED |
return 1; |
| 229 | } | |
| 230 | ||
| 231 | private static void warnMismatches(Set<String> inCsvNotSource, Set<String> inSourceNotCsv) { | |
| 232 | if (LOG.isLoggable(Level.WARNING)) { | |
| 233 | for (String k : inCsvNotSource) { | |
| 234 | LOG.warning("Mismatch (in CSV, not found in source): " + k); | |
| 235 | } | |
| 236 | for (String k : inSourceNotCsv) { | |
| 237 | LOG.warning("Mismatch (in source, not present in CSV): " + k); | |
| 238 | } | |
| 239 | } | |
| 240 | } | |
| 241 | ||
| 242 | private static int applyFilesLoop(Map<Path, List<MethodKey>> sourceIndex, | |
| 243 | Map<String, ScanRecord> desiredState, List<SourcePatcher> patchers, | |
| 244 | int mismatchCount, PrintWriter log, boolean verbose, boolean promoteAi) { | |
| 245 | int modifiedFiles = 0; | |
| 246 | int totalChanges = 0; | |
| 247 | int skippedFiles = 0; | |
| 248 | int totalPromoted = 0; | |
| 249 | boolean hadErrors = false; | |
| 250 | ||
| 251 | for (Map.Entry<Path, List<MethodKey>> entry : sourceIndex.entrySet()) { | |
| 252 | FileOutcome outcome = processFile(entry.getKey(), entry.getValue(), | |
| 253 | desiredState, patchers, log, verbose, promoteAi); | |
| 254 |
1
1. applyFilesLoop : Replaced integer addition with subtraction → SURVIVED |
totalChanges += outcome.changes(); |
| 255 |
1
1. applyFilesLoop : Replaced integer addition with subtraction → SURVIVED |
totalPromoted += outcome.promoted(); |
| 256 |
3
1. applyFilesLoop : removed conditional - replaced comparison check with false → SURVIVED 2. applyFilesLoop : changed conditional boundary → KILLED 3. applyFilesLoop : removed conditional - replaced comparison check with true → KILLED |
if (outcome.changes() > 0) { |
| 257 |
1
1. applyFilesLoop : Changed increment from 1 to -1 → SURVIVED |
modifiedFiles++; |
| 258 | } | |
| 259 |
2
1. applyFilesLoop : removed conditional - replaced equality check with false → SURVIVED 2. applyFilesLoop : removed conditional - replaced equality check with true → SURVIVED |
if (outcome.skipped()) { |
| 260 |
1
1. applyFilesLoop : Changed increment from 1 to -1 → NO_COVERAGE |
skippedFiles++; |
| 261 | } | |
| 262 |
2
1. applyFilesLoop : removed conditional - replaced equality check with false → SURVIVED 2. applyFilesLoop : removed conditional - replaced equality check with true → KILLED |
if (outcome.error()) { |
| 263 | hadErrors = true; | |
| 264 | } | |
| 265 | } | |
| 266 | ||
| 267 | // Capacity 320 comfortably covers the worst-case message: | |
| 268 | // "Apply-tags-from-csv complete: <int> change(s) in <int> file(s); | |
| 269 | // <int> mismatch(es) skipped. <int> file(s) skipped (no source | |
| 270 | // write-back support for the language). <int> value(s) promoted from | |
| 271 | // AI columns (unvalidated — used with -promote-ai)." | |
| 272 | StringBuilder summary = new StringBuilder(320) | |
| 273 | .append("Apply-tags-from-csv complete: ") | |
| 274 | .append(totalChanges).append(" change(s) in ") | |
| 275 | .append(modifiedFiles).append(" file(s); ") | |
| 276 | .append(mismatchCount).append(" mismatch(es) skipped."); | |
| 277 |
3
1. applyFilesLoop : removed conditional - replaced comparison check with true → SURVIVED 2. applyFilesLoop : changed conditional boundary → SURVIVED 3. applyFilesLoop : removed conditional - replaced comparison check with false → SURVIVED |
if (skippedFiles > 0) { |
| 278 | summary.append(' ').append(skippedFiles) | |
| 279 | .append(" file(s) skipped (no source write-back support for the language)."); | |
| 280 | } | |
| 281 |
2
1. applyFilesLoop : removed conditional - replaced equality check with true → SURVIVED 2. applyFilesLoop : removed conditional - replaced equality check with false → KILLED |
if (promoteAi) { |
| 282 | summary.append(' ').append(totalPromoted) | |
| 283 | .append(" value(s) promoted from AI columns (unvalidated — used with -promote-ai)."); | |
| 284 | } | |
| 285 |
1
1. applyFilesLoop : removed call to java/io/PrintWriter::println → KILLED |
log.println(summary.toString()); |
| 286 |
4
1. applyFilesLoop : removed conditional - replaced equality check with true → SURVIVED 2. applyFilesLoop : removed conditional - replaced equality check with true → SURVIVED 3. applyFilesLoop : removed conditional - replaced equality check with false → KILLED 4. applyFilesLoop : removed conditional - replaced equality check with false → KILLED |
if (totalChanges == 0 && !verbose) { |
| 287 |
1
1. applyFilesLoop : removed call to java/io/PrintWriter::println → KILLED |
log.println("Hint: no methods were updated. Re-run with -verbose to print the CSV keys, " |
| 288 | + "the keys discovered in the source, and the key-by-key match result — the " | |
| 289 | + "usual cause is a fully qualified class name or working-directory mismatch."); | |
| 290 | } | |
| 291 |
2
1. applyFilesLoop : removed conditional - replaced equality check with false → SURVIVED 2. applyFilesLoop : removed conditional - replaced equality check with true → KILLED |
return hadErrors ? 1 : 0; |
| 292 | } | |
| 293 | ||
| 294 | /** | |
| 295 | * Applies the desired state to a single source file and reports the outcome. | |
| 296 | * | |
| 297 | * @param path source file to patch | |
| 298 | * @param methods methods discovered in {@code path} | |
| 299 | * @param desiredState CSV desired state keyed by {@code <fqcn>::<method>} | |
| 300 | * @param patchers configured source patchers | |
| 301 | * @param log progress writer | |
| 302 | * @param verbose whether to print per-file diagnostics | |
| 303 | * @param promoteAi whether to fall back to the AI columns for blank | |
| 304 | * curated values (risky; see the class Javadoc) | |
| 305 | * @return the outcome (change count, promotion count, language-skip flag, | |
| 306 | * error flag) | |
| 307 | */ | |
| 308 | private static FileOutcome processFile(Path path, List<MethodKey> methods, | |
| 309 | Map<String, ScanRecord> desiredState, List<SourcePatcher> patchers, | |
| 310 | PrintWriter log, boolean verbose, boolean promoteAi) { | |
| 311 |
2
1. processFile : removed conditional - replaced equality check with false → SURVIVED 2. processFile : removed conditional - replaced equality check with true → KILLED |
if (!hasRelevantMethod(methods, desiredState)) { |
| 312 |
2
1. processFile : removed conditional - replaced equality check with true → SURVIVED 2. processFile : removed conditional - replaced equality check with false → SURVIVED |
if (verbose) { |
| 313 |
1
1. processFile : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] no CSV row matches any method in " + path + " — skipping"); |
| 314 | } | |
| 315 |
1
1. processFile : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::processFile → KILLED |
return FileOutcome.NONE; |
| 316 | } | |
| 317 | ||
| 318 | SourcePatcher patcher = patchers.stream() | |
| 319 |
2
1. lambda$processFile$0 : replaced boolean return with true for org/egothor/methodatlas/ApplyTagsFromCsvEngine::lambda$processFile$0 → SURVIVED 2. lambda$processFile$0 : replaced boolean return with false for org/egothor/methodatlas/ApplyTagsFromCsvEngine::lambda$processFile$0 → KILLED |
.filter(p -> p.supports(path)) |
| 320 | .findFirst().orElse(null); | |
| 321 |
2
1. processFile : removed conditional - replaced equality check with false → SURVIVED 2. processFile : removed conditional - replaced equality check with true → KILLED |
if (patcher == null) { |
| 322 | if (LOG.isLoggable(Level.INFO)) { | |
| 323 | LOG.log(Level.INFO, "Skipping {0}: no SourcePatcher available for this language", path); | |
| 324 | } | |
| 325 |
1
1. processFile : removed call to java/io/PrintWriter::println → NO_COVERAGE |
log.println("Apply-tags-from-csv: skipped " + path |
| 326 | + " — source write-back is not supported for this language " | |
| 327 | + "(currently Java and C# only)"); | |
| 328 |
1
1. processFile : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::processFile → NO_COVERAGE |
return FileOutcome.forSkip(); |
| 329 | } | |
| 330 | ||
| 331 | Map<String, List<String>> tagsToApply = new LinkedHashMap<>(); | |
| 332 | Map<String, String> displayNames = new LinkedHashMap<>(); | |
| 333 | int promoted = collectDesiredState(path, methods, desiredState, promoteAi, | |
| 334 | tagsToApply, displayNames, log, verbose); | |
| 335 | ||
| 336 |
2
1. processFile : removed conditional - replaced equality check with true → SURVIVED 2. processFile : removed conditional - replaced equality check with false → SURVIVED |
if (verbose) { |
| 337 |
1
1. processFile : removed call to java/io/PrintWriter::println → NO_COVERAGE |
log.println("[verbose] applying to " + path + ": " + tagsToApply.size() |
| 338 | + " method(s) with a desired tag-set, " + displayNames.size() | |
| 339 | + " with a desired display-name" | |
| 340 |
2
1. processFile : removed conditional - replaced equality check with false → NO_COVERAGE 2. processFile : removed conditional - replaced equality check with true → NO_COVERAGE |
+ (promoteAi ? "; " + promoted + " value(s) promoted from AI columns" : "")); |
| 341 | } | |
| 342 | try { | |
| 343 | int changes = patcher.patch(path, tagsToApply, displayNames, log); | |
| 344 |
2
1. processFile : removed conditional - replaced equality check with true → SURVIVED 2. processFile : removed conditional - replaced equality check with false → SURVIVED |
if (verbose) { |
| 345 |
1
1. processFile : removed call to java/io/PrintWriter::println → NO_COVERAGE |
log.println("[verbose] -> " + changes + " change(s) written to " + path); |
| 346 | } | |
| 347 |
1
1. processFile : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::processFile → KILLED |
return FileOutcome.forChanges(changes, promoted); |
| 348 | } catch (IOException e) { | |
| 349 | if (LOG.isLoggable(Level.WARNING)) { | |
| 350 | LOG.log(Level.WARNING, "Cannot process: " + path, e); | |
| 351 | } | |
| 352 |
1
1. processFile : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::processFile → NO_COVERAGE |
return FileOutcome.forError(); |
| 353 | } | |
| 354 | } | |
| 355 | ||
| 356 | /** | |
| 357 | * Fills the {@code tagsToApply} and {@code displayNames} maps from the CSV | |
| 358 | * desired state for every method discovered in {@code path}, applying | |
| 359 | * AI-column promotion when {@code promoteAi} is set. | |
| 360 | * | |
| 361 | * <p> | |
| 362 | * The {@code displayNames} value carries the established three-way contract: | |
| 363 | * {@code null} (column absent) leaves {@code @DisplayName} untouched and is | |
| 364 | * therefore not put into the map; {@code ""} removes it; non-empty text sets | |
| 365 | * it. Promotion only substitutes an AI value when the curated value is blank | |
| 366 | * and a non-blank AI value is present, so the contract is preserved for | |
| 367 | * every method the operator curated by hand. | |
| 368 | * </p> | |
| 369 | * | |
| 370 | * @param path source file being processed (used for verbose notices) | |
| 371 | * @param methods methods discovered in {@code path} | |
| 372 | * @param desiredState CSV desired state keyed by {@code <fqcn>::<method>} | |
| 373 | * @param promoteAi whether to fall back to the AI columns for blank values | |
| 374 | * @param tagsToApply output map from method name to desired tag list | |
| 375 | * @param displayNames output map from method name to desired display name | |
| 376 | * @param log progress writer for verbose per-promotion notices | |
| 377 | * @param verbose whether to itemise each promotion | |
| 378 | * @return the number of values promoted from the AI columns | |
| 379 | */ | |
| 380 | private static int collectDesiredState(Path path, List<MethodKey> methods, | |
| 381 | Map<String, ScanRecord> desiredState, boolean promoteAi, | |
| 382 | Map<String, List<String>> tagsToApply, Map<String, String> displayNames, | |
| 383 | PrintWriter log, boolean verbose) { | |
| 384 | int promoted = 0; | |
| 385 | for (MethodKey mk : methods) { | |
| 386 | ScanRecord desired = desiredState.get(key(mk.fqcn(), mk.method())); | |
| 387 |
2
1. collectDesiredState : removed conditional - replaced equality check with false → SURVIVED 2. collectDesiredState : removed conditional - replaced equality check with true → KILLED |
if (desired == null) { |
| 388 | continue; | |
| 389 | } | |
| 390 | Resolved<List<String>> tags = resolveTags(desired, promoteAi); | |
| 391 |
2
1. collectDesiredState : removed conditional - replaced equality check with true → SURVIVED 2. collectDesiredState : removed conditional - replaced equality check with false → KILLED |
if (tags.value() != null) { |
| 392 | tagsToApply.put(mk.method(), tags.value()); | |
| 393 | } | |
| 394 | Resolved<String> name = resolveDisplayName(desired, promoteAi); | |
| 395 |
2
1. collectDesiredState : removed conditional - replaced equality check with true → SURVIVED 2. collectDesiredState : removed conditional - replaced equality check with false → KILLED |
if (name.value() != null) { |
| 396 | displayNames.put(mk.method(), name.value()); | |
| 397 | } | |
| 398 |
2
1. collectDesiredState : removed conditional - replaced equality check with true → SURVIVED 2. collectDesiredState : removed conditional - replaced equality check with false → SURVIVED |
if (tags.promoted()) { |
| 399 |
1
1. collectDesiredState : Changed increment from 1 to -1 → SURVIVED |
promoted++; |
| 400 |
1
1. collectDesiredState : removed call to org/egothor/methodatlas/ApplyTagsFromCsvEngine::logPromotion → SURVIVED |
logPromotion(log, verbose, path, mk, "tags <- ai_tags"); |
| 401 | } | |
| 402 |
2
1. collectDesiredState : removed conditional - replaced equality check with true → SURVIVED 2. collectDesiredState : removed conditional - replaced equality check with false → SURVIVED |
if (name.promoted()) { |
| 403 |
1
1. collectDesiredState : Changed increment from 1 to -1 → SURVIVED |
promoted++; |
| 404 |
1
1. collectDesiredState : removed call to org/egothor/methodatlas/ApplyTagsFromCsvEngine::logPromotion → SURVIVED |
logPromotion(log, verbose, path, mk, "display_name <- ai_display_name"); |
| 405 | } | |
| 406 | } | |
| 407 |
1
1. collectDesiredState : replaced int return with 0 for org/egothor/methodatlas/ApplyTagsFromCsvEngine::collectDesiredState → SURVIVED |
return promoted; |
| 408 | } | |
| 409 | ||
| 410 | /** | |
| 411 | * Resolves the desired tag list for a record, optionally promoting the | |
| 412 | * {@code ai_tags} column when the curated {@code tags} column is blank. | |
| 413 | * | |
| 414 | * @param record CSV record for the method | |
| 415 | * @param promoteAi whether AI promotion is enabled | |
| 416 | * @return the resolved tag list and whether it came from the AI column | |
| 417 | */ | |
| 418 | private static Resolved<List<String>> resolveTags(ScanRecord record, boolean promoteAi) { | |
| 419 | List<String> curated = record.tags(); | |
| 420 |
4
1. resolveTags : removed conditional - replaced equality check with true → SURVIVED 2. resolveTags : removed conditional - replaced equality check with false → KILLED 3. resolveTags : removed conditional - replaced equality check with false → KILLED 4. resolveTags : removed conditional - replaced equality check with true → KILLED |
boolean curatedBlank = curated == null || curated.isEmpty(); |
| 421 | List<String> ai = record.aiTags(); | |
| 422 |
8
1. resolveTags : removed conditional - replaced equality check with true → SURVIVED 2. resolveTags : removed conditional - replaced equality check with true → SURVIVED 3. resolveTags : removed conditional - replaced equality check with true → KILLED 4. resolveTags : removed conditional - replaced equality check with false → KILLED 5. resolveTags : removed conditional - replaced equality check with true → KILLED 6. resolveTags : removed conditional - replaced equality check with false → KILLED 7. resolveTags : removed conditional - replaced equality check with false → KILLED 8. resolveTags : removed conditional - replaced equality check with false → KILLED |
if (promoteAi && curatedBlank && ai != null && !ai.isEmpty()) { |
| 423 |
1
1. resolveTags : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::resolveTags → KILLED |
return new Resolved<>(ai, true); |
| 424 | } | |
| 425 |
1
1. resolveTags : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::resolveTags → KILLED |
return new Resolved<>(curated, false); |
| 426 | } | |
| 427 | ||
| 428 | /** | |
| 429 | * Resolves the desired display name for a record, optionally promoting the | |
| 430 | * {@code ai_display_name} column when the curated {@code display_name} column | |
| 431 | * is blank. | |
| 432 | * | |
| 433 | * @param record CSV record for the method | |
| 434 | * @param promoteAi whether AI promotion is enabled | |
| 435 | * @return the resolved display name and whether it came from the AI column | |
| 436 | */ | |
| 437 | private static Resolved<String> resolveDisplayName(ScanRecord record, boolean promoteAi) { | |
| 438 | String curated = record.displayName(); | |
| 439 |
4
1. resolveDisplayName : removed conditional - replaced equality check with false → SURVIVED 2. resolveDisplayName : removed conditional - replaced equality check with true → SURVIVED 3. resolveDisplayName : removed conditional - replaced equality check with false → KILLED 4. resolveDisplayName : removed conditional - replaced equality check with true → KILLED |
boolean curatedBlank = curated == null || curated.isBlank(); |
| 440 | String ai = record.aiDisplayName(); | |
| 441 |
8
1. resolveDisplayName : removed conditional - replaced equality check with true → SURVIVED 2. resolveDisplayName : removed conditional - replaced equality check with true → SURVIVED 3. resolveDisplayName : removed conditional - replaced equality check with false → KILLED 4. resolveDisplayName : removed conditional - replaced equality check with true → KILLED 5. resolveDisplayName : removed conditional - replaced equality check with false → KILLED 6. resolveDisplayName : removed conditional - replaced equality check with true → KILLED 7. resolveDisplayName : removed conditional - replaced equality check with false → KILLED 8. resolveDisplayName : removed conditional - replaced equality check with false → KILLED |
if (promoteAi && curatedBlank && ai != null && !ai.isBlank()) { |
| 442 |
1
1. resolveDisplayName : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::resolveDisplayName → KILLED |
return new Resolved<>(ai, true); |
| 443 | } | |
| 444 |
1
1. resolveDisplayName : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine::resolveDisplayName → KILLED |
return new Resolved<>(curated, false); |
| 445 | } | |
| 446 | ||
| 447 | /** | |
| 448 | * Writes a per-promotion audit notice under {@code -verbose}. | |
| 449 | * | |
| 450 | * @param log progress writer | |
| 451 | * @param verbose whether verbose output is enabled | |
| 452 | * @param path source file being processed | |
| 453 | * @param mk method whose value was promoted | |
| 454 | * @param detail short description of the promoted field | |
| 455 | */ | |
| 456 | private static void logPromotion(PrintWriter log, boolean verbose, Path path, | |
| 457 | MethodKey mk, String detail) { | |
| 458 |
2
1. logPromotion : removed conditional - replaced equality check with false → SURVIVED 2. logPromotion : removed conditional - replaced equality check with true → SURVIVED |
if (verbose) { |
| 459 |
1
1. logPromotion : removed call to java/io/PrintWriter::println → NO_COVERAGE |
log.println("[verbose] [promote-ai] " + path + " " + mk.fqcn() + "::" |
| 460 | + mk.method() + " " + detail); | |
| 461 | } | |
| 462 | } | |
| 463 | ||
| 464 | /** | |
| 465 | * A resolved desired-state value paired with whether it was promoted from an | |
| 466 | * AI column rather than taken from a curated column. | |
| 467 | * | |
| 468 | * @param <T> the value type ({@code List<String>} for tags, | |
| 469 | * {@code String} for display name) | |
| 470 | * @param value the resolved value; may be {@code null} for display name | |
| 471 | * when the column was absent | |
| 472 | * @param promoted {@code true} when the value came from an AI column | |
| 473 | */ | |
| 474 | private record Resolved<T>(T value, boolean promoted) { | |
| 475 | } | |
| 476 | ||
| 477 | /** | |
| 478 | * Outcome of patching one source file: the number of changes written, the | |
| 479 | * number of values promoted from AI columns, and whether the file was | |
| 480 | * skipped for lack of a {@link SourcePatcher} or failed with an I/O error. | |
| 481 | * | |
| 482 | * @param changes number of annotation changes written (zero when none applied) | |
| 483 | * @param promoted number of desired-state values sourced from the AI columns | |
| 484 | * @param skipped {@code true} when skipped because no patcher supports the language | |
| 485 | * @param error {@code true} when patching threw an {@link IOException} | |
| 486 | */ | |
| 487 | private record FileOutcome(int changes, int promoted, boolean skipped, boolean error) { | |
| 488 | private static final FileOutcome NONE = new FileOutcome(0, 0, false, false); | |
| 489 | ||
| 490 | private static FileOutcome forSkip() { | |
| 491 |
1
1. forSkip : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine$FileOutcome::forSkip → NO_COVERAGE |
return new FileOutcome(0, 0, true, false); |
| 492 | } | |
| 493 | ||
| 494 | private static FileOutcome forError() { | |
| 495 |
1
1. forError : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine$FileOutcome::forError → NO_COVERAGE |
return new FileOutcome(0, 0, false, true); |
| 496 | } | |
| 497 | ||
| 498 | private static FileOutcome forChanges(int changes, int promoted) { | |
| 499 |
1
1. forChanges : replaced return value with null for org/egothor/methodatlas/ApplyTagsFromCsvEngine$FileOutcome::forChanges → KILLED |
return new FileOutcome(changes, promoted, false, false); |
| 500 | } | |
| 501 | } | |
| 502 | ||
| 503 | private static boolean hasRelevantMethod(List<MethodKey> methods, Map<String, ScanRecord> desiredState) { | |
| 504 | for (MethodKey mk : methods) { | |
| 505 |
2
1. hasRelevantMethod : removed conditional - replaced equality check with true → SURVIVED 2. hasRelevantMethod : removed conditional - replaced equality check with false → KILLED |
if (desiredState.containsKey(key(mk.fqcn(), mk.method()))) { |
| 506 |
1
1. hasRelevantMethod : replaced boolean return with false for org/egothor/methodatlas/ApplyTagsFromCsvEngine::hasRelevantMethod → KILLED |
return true; |
| 507 | } | |
| 508 | } | |
| 509 |
1
1. hasRelevantMethod : replaced boolean return with true for org/egothor/methodatlas/ApplyTagsFromCsvEngine::hasRelevantMethod → SURVIVED |
return false; |
| 510 | } | |
| 511 | ||
| 512 | /** | |
| 513 | * Scans source roots and builds an index mapping each source file to the | |
| 514 | * list of (FQCN, method) pairs for all test methods it contains. | |
| 515 | * | |
| 516 | * <p> | |
| 517 | * Each supported source file is passed to | |
| 518 | * {@link SourcePatcher#discoverMethodsByClass(Path)} to obtain the actual | |
| 519 | * test-method inventory. This gives the correct FQCN–method pairs | |
| 520 | * regardless of whether the source tree uses the standard | |
| 521 | * {@code package/to/ClassName.java} directory structure. | |
| 522 | * </p> | |
| 523 | * | |
| 524 | * @param roots source root directories | |
| 525 | * @param patchers list of configured patchers used to identify supported files | |
| 526 | * @return map from source file path to list of {@link MethodKey} entries; | |
| 527 | * never {@code null} | |
| 528 | * @throws IOException if a file tree cannot be traversed | |
| 529 | */ | |
| 530 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") | |
| 531 | private static Map<Path, List<MethodKey>> buildSourceIndex( | |
| 532 | List<Path> roots, | |
| 533 | List<SourcePatcher> patchers) throws IOException { | |
| 534 | ||
| 535 | Map<Path, List<MethodKey>> index = new HashMap<>(); | |
| 536 | ||
| 537 | for (Path root : roots) { | |
| 538 |
2
1. buildSourceIndex : removed conditional - replaced equality check with false → SURVIVED 2. buildSourceIndex : removed conditional - replaced equality check with true → KILLED |
if (!Files.isDirectory(root)) { |
| 539 | continue; | |
| 540 | } | |
| 541 | try (Stream<Path> stream = Files.walk(root)) { | |
| 542 | for (Path path : (Iterable<Path>) stream | |
| 543 |
2
1. lambda$buildSourceIndex$1 : replaced boolean return with true for org/egothor/methodatlas/ApplyTagsFromCsvEngine::lambda$buildSourceIndex$1 → SURVIVED 2. lambda$buildSourceIndex$1 : replaced boolean return with false for org/egothor/methodatlas/ApplyTagsFromCsvEngine::lambda$buildSourceIndex$1 → KILLED |
.filter(Files::isRegularFile)::iterator) { |
| 544 | ||
| 545 | SourcePatcher patcher = patchers.stream() | |
| 546 |
2
1. lambda$buildSourceIndex$2 : replaced boolean return with true for org/egothor/methodatlas/ApplyTagsFromCsvEngine::lambda$buildSourceIndex$2 → SURVIVED 2. lambda$buildSourceIndex$2 : replaced boolean return with false for org/egothor/methodatlas/ApplyTagsFromCsvEngine::lambda$buildSourceIndex$2 → KILLED |
.filter(p -> p.supports(path)) |
| 547 | .findFirst().orElse(null); | |
| 548 |
2
1. buildSourceIndex : removed conditional - replaced equality check with true → KILLED 2. buildSourceIndex : removed conditional - replaced equality check with false → KILLED |
if (patcher == null) { |
| 549 | continue; | |
| 550 | } | |
| 551 | ||
| 552 | // Ask the patcher to discover test methods in this file | |
| 553 | Map<String, List<String>> byClass = patcher.discoverMethodsByClass(path); | |
| 554 | List<MethodKey> keys = new ArrayList<>(); | |
| 555 | for (Map.Entry<String, List<String>> classEntry : byClass.entrySet()) { | |
| 556 | String fqcn = classEntry.getKey(); | |
| 557 | for (String methodName : classEntry.getValue()) { | |
| 558 | keys.add(new MethodKey(fqcn, methodName)); | |
| 559 | } | |
| 560 | } | |
| 561 |
2
1. buildSourceIndex : removed conditional - replaced equality check with true → SURVIVED 2. buildSourceIndex : removed conditional - replaced equality check with false → KILLED |
if (!keys.isEmpty()) { |
| 562 | index.put(path, keys); | |
| 563 | } | |
| 564 | } | |
| 565 | } | |
| 566 | } | |
| 567 | ||
| 568 |
1
1. buildSourceIndex : replaced return value with Collections.emptyMap for org/egothor/methodatlas/ApplyTagsFromCsvEngine::buildSourceIndex → KILLED |
return index; |
| 569 | } | |
| 570 | ||
| 571 | /** | |
| 572 | * Prints, under {@code -verbose}, the run context and the complete set of | |
| 573 | * desired-state keys loaded from the CSV. | |
| 574 | * | |
| 575 | * @param log progress writer | |
| 576 | * @param csvFile reviewed CSV path | |
| 577 | * @param roots configured scan roots | |
| 578 | * @param desiredState CSV-derived desired state keyed by {@code <fqcn>::<method>} | |
| 579 | */ | |
| 580 | private static void logVerboseHeader(PrintWriter log, Path csvFile, List<Path> roots, | |
| 581 | Map<String, ScanRecord> desiredState) { | |
| 582 |
1
1. logVerboseHeader : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] working directory: " + Paths.get("").toAbsolutePath()); |
| 583 |
1
1. logVerboseHeader : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] CSV file: " + csvFile.toAbsolutePath()); |
| 584 | for (Path root : roots) { | |
| 585 |
2
1. logVerboseHeader : removed conditional - replaced equality check with false → SURVIVED 2. logVerboseHeader : removed conditional - replaced equality check with true → SURVIVED |
String note = Files.isDirectory(root) ? "" : " (NOT an existing directory)"; |
| 586 |
1
1. logVerboseHeader : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] scan root: " + root.toAbsolutePath() + note); |
| 587 | } | |
| 588 |
1
1. logVerboseHeader : removed call to java/io/PrintWriter::println → KILLED |
log.println("[verbose] CSV desired-state keys (" + desiredState.size() |
| 589 | + "); lookup format is <fqcn>::<method>:"); | |
| 590 | desiredState.keySet().stream().sorted() | |
| 591 |
2
1. lambda$logVerboseHeader$3 : removed call to java/io/PrintWriter::println → SURVIVED 2. logVerboseHeader : removed call to java/util/stream/Stream::forEach → SURVIVED |
.forEach(k -> log.println("[verbose] CSV " + k)); |
| 592 | } | |
| 593 | ||
| 594 | /** | |
| 595 | * Prints, under {@code -verbose}, the keys discovered in the scanned source | |
| 596 | * tree (the right-hand side of the lookup). | |
| 597 | * | |
| 598 | * @param log progress writer | |
| 599 | * @param sourceIndex source file to discovered methods | |
| 600 | * @param sourceKeys flattened {@code <fqcn>::<method>} keys discovered in source | |
| 601 | */ | |
| 602 | private static void logVerboseSource(PrintWriter log, Map<Path, List<MethodKey>> sourceIndex, | |
| 603 | Set<String> sourceKeys) { | |
| 604 |
1
1. logVerboseSource : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] source files with discoverable test methods: " + sourceIndex.size()); |
| 605 |
1
1. logVerboseSource : removed call to java/io/PrintWriter::println → KILLED |
log.println("[verbose] source keys (" + sourceKeys.size() + "):"); |
| 606 |
2
1. logVerboseSource : removed call to java/util/stream/Stream::forEach → SURVIVED 2. lambda$logVerboseSource$4 : removed call to java/io/PrintWriter::println → SURVIVED |
sourceKeys.stream().sorted().forEach(k -> log.println("[verbose] SRC " + k)); |
| 607 |
2
1. logVerboseSource : removed conditional - replaced equality check with true → SURVIVED 2. logVerboseSource : removed conditional - replaced equality check with false → SURVIVED |
if (sourceKeys.isEmpty()) { |
| 608 |
1
1. logVerboseSource : removed call to java/io/PrintWriter::println → NO_COVERAGE |
log.println("[verbose] No test methods were discovered under the scan root(s). " |
| 609 | + "Confirm the command is run from the correct directory and that the files " | |
| 610 | + "use a language with source write-back support (Java, C#)."); | |
| 611 | } | |
| 612 | } | |
| 613 | ||
| 614 | /** | |
| 615 | * Prints, under {@code -verbose}, the key-by-key match result so an operator | |
| 616 | * can see exactly which CSV rows failed to line up with a source method. | |
| 617 | * | |
| 618 | * @param log progress writer | |
| 619 | * @param desiredCount number of CSV desired-state keys | |
| 620 | * @param inCsvNotSource keys present in the CSV but absent from source | |
| 621 | * @param inSourceNotCsv keys present in source but absent from the CSV | |
| 622 | */ | |
| 623 | private static void logVerboseMatch(PrintWriter log, int desiredCount, | |
| 624 | Set<String> inCsvNotSource, Set<String> inSourceNotCsv) { | |
| 625 |
1
1. logVerboseMatch : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] matched keys (present in both CSV and source): " |
| 626 |
1
1. logVerboseMatch : Replaced integer subtraction with addition → SURVIVED |
+ (desiredCount - inCsvNotSource.size())); |
| 627 |
1
1. logVerboseMatch : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] in CSV but NOT found in source (" + inCsvNotSource.size() + "):"); |
| 628 |
2
1. logVerboseMatch : removed call to java/util/stream/Stream::forEach → KILLED 2. lambda$logVerboseMatch$5 : removed call to java/io/PrintWriter::println → KILLED |
inCsvNotSource.stream().sorted().forEach(k -> log.println("[verbose] CSV-only " + k)); |
| 629 |
1
1. logVerboseMatch : removed call to java/io/PrintWriter::println → SURVIVED |
log.println("[verbose] in source but NOT present in CSV (" + inSourceNotCsv.size() + "):"); |
| 630 |
2
1. lambda$logVerboseMatch$6 : removed call to java/io/PrintWriter::println → KILLED 2. logVerboseMatch : removed call to java/util/stream/Stream::forEach → KILLED |
inSourceNotCsv.stream().sorted().forEach(k -> log.println("[verbose] SRC-only " + k)); |
| 631 | } | |
| 632 | ||
| 633 | private static String key(String fqcn, String method) { | |
| 634 |
1
1. key : replaced return value with "" for org/egothor/methodatlas/ApplyTagsFromCsvEngine::key → KILLED |
return fqcn + "::" + method; |
| 635 | } | |
| 636 | ||
| 637 | /** Lightweight tuple holding a fully qualified class name and a method name. */ | |
| 638 | private record MethodKey(String fqcn, String method) { | |
| 639 | } | |
| 640 | } | |
Mutations | ||
| 158 |
1.1 2.2 |
|
| 159 |
1.1 |
|
| 163 |
1.1 |
|
| 168 |
1.1 2.2 |
|
| 169 |
1.1 |
|
| 170 |
1.1 2.2 |
|
| 171 |
1.1 |
|
| 189 |
1.1 2.2 |
|
| 190 |
1.1 |
|
| 200 |
1.1 |
|
| 202 |
1.1 2.2 |
|
| 203 |
1.1 |
|
| 207 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 208 |
1.1 |
|
| 212 |
1.1 |
|
| 215 |
1.1 |
|
| 221 |
1.1 |
|
| 224 |
1.1 |
|
| 226 |
1.1 |
|
| 228 |
1.1 |
|
| 254 |
1.1 |
|
| 255 |
1.1 |
|
| 256 |
1.1 2.2 3.3 |
|
| 257 |
1.1 |
|
| 259 |
1.1 2.2 |
|
| 260 |
1.1 |
|
| 262 |
1.1 2.2 |
|
| 277 |
1.1 2.2 3.3 |
|
| 281 |
1.1 2.2 |
|
| 285 |
1.1 |
|
| 286 |
1.1 2.2 3.3 4.4 |
|
| 287 |
1.1 |
|
| 291 |
1.1 2.2 |
|
| 311 |
1.1 2.2 |
|
| 312 |
1.1 2.2 |
|
| 313 |
1.1 |
|
| 315 |
1.1 |
|
| 319 |
1.1 2.2 |
|
| 321 |
1.1 2.2 |
|
| 325 |
1.1 |
|
| 328 |
1.1 |
|
| 336 |
1.1 2.2 |
|
| 337 |
1.1 |
|
| 340 |
1.1 2.2 |
|
| 344 |
1.1 2.2 |
|
| 345 |
1.1 |
|
| 347 |
1.1 |
|
| 352 |
1.1 |
|
| 387 |
1.1 2.2 |
|
| 391 |
1.1 2.2 |
|
| 395 |
1.1 2.2 |
|
| 398 |
1.1 2.2 |
|
| 399 |
1.1 |
|
| 400 |
1.1 |
|
| 402 |
1.1 2.2 |
|
| 403 |
1.1 |
|
| 404 |
1.1 |
|
| 407 |
1.1 |
|
| 420 |
1.1 2.2 3.3 4.4 |
|
| 422 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 |
|
| 423 |
1.1 |
|
| 425 |
1.1 |
|
| 439 |
1.1 2.2 3.3 4.4 |
|
| 441 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 |
|
| 442 |
1.1 |
|
| 444 |
1.1 |
|
| 458 |
1.1 2.2 |
|
| 459 |
1.1 |
|
| 491 |
1.1 |
|
| 495 |
1.1 |
|
| 499 |
1.1 |
|
| 505 |
1.1 2.2 |
|
| 506 |
1.1 |
|
| 509 |
1.1 |
|
| 538 |
1.1 2.2 |
|
| 543 |
1.1 2.2 |
|
| 546 |
1.1 2.2 |
|
| 548 |
1.1 2.2 |
|
| 561 |
1.1 2.2 |
|
| 568 |
1.1 |
|
| 582 |
1.1 |
|
| 583 |
1.1 |
|
| 585 |
1.1 2.2 |
|
| 586 |
1.1 |
|
| 588 |
1.1 |
|
| 591 |
1.1 2.2 |
|
| 604 |
1.1 |
|
| 605 |
1.1 |
|
| 606 |
1.1 2.2 |
|
| 607 |
1.1 2.2 |
|
| 608 |
1.1 |
|
| 625 |
1.1 |
|
| 626 |
1.1 |
|
| 627 |
1.1 |
|
| 628 |
1.1 2.2 |
|
| 629 |
1.1 |
|
| 630 |
1.1 2.2 |
|
| 634 |
1.1 |