|
1
|
|
package org.egothor.methodatlas; |
|
2
|
|
|
|
3
|
|
import java.io.BufferedWriter; |
|
4
|
|
import java.io.IOException; |
|
5
|
|
import java.nio.charset.StandardCharsets; |
|
6
|
|
import java.nio.file.Files; |
|
7
|
|
import java.nio.file.Path; |
|
8
|
|
import java.util.ArrayList; |
|
9
|
|
import java.util.Collection; |
|
10
|
|
import java.util.List; |
|
11
|
|
import java.util.logging.Level; |
|
12
|
|
import java.util.logging.Logger; |
|
13
|
|
|
|
14
|
|
import tools.jackson.core.JacksonException; |
|
15
|
|
import tools.jackson.databind.json.JsonMapper; |
|
16
|
|
|
|
17
|
|
/** |
|
18
|
|
* Reads and writes the unified AI result cache as a JSON Lines file: one |
|
19
|
|
* {@link AiCacheEntry} per line. |
|
20
|
|
* |
|
21
|
|
* <p> |
|
22
|
|
* JSON Lines is chosen over a single JSON array so the file streams, appends |
|
23
|
|
* cleanly, and a single corrupt line degrades to one lost cache entry rather than |
|
24
|
|
* an unreadable file. The format is independent of the stable per-method scan CSV |
|
25
|
|
* (which cannot carry credential verdicts or a prompt signature) and of the |
|
26
|
|
* separate credential CSV; it is the sole carrier of cache state. |
|
27
|
|
* </p> |
|
28
|
|
* |
|
29
|
|
* <p> |
|
30
|
|
* This class is a stateless, thread-safe utility holder. |
|
31
|
|
* </p> |
|
32
|
|
* |
|
33
|
|
* @since 4.1.0 |
|
34
|
|
*/ |
|
35
|
|
public final class AiCacheStore { |
|
36
|
|
|
|
37
|
|
private static final Logger LOG = Logger.getLogger(AiCacheStore.class.getName()); |
|
38
|
|
|
|
39
|
|
/** Shared, thread-safe JSON mapper (immutable after build). */ |
|
40
|
|
private static final JsonMapper MAPPER = JsonMapper.builder().build(); |
|
41
|
|
|
|
42
|
|
private AiCacheStore() { |
|
43
|
|
// utility class |
|
44
|
|
} |
|
45
|
|
|
|
46
|
|
/** |
|
47
|
|
* Returns {@code true} if {@code file} appears to be a unified JSON-Lines cache |
|
48
|
|
* rather than a legacy scan CSV, by inspecting the first non-whitespace byte. |
|
49
|
|
* |
|
50
|
|
* @param file path to inspect; never {@code null} |
|
51
|
|
* @return {@code true} when the first non-blank character is an opening brace |
|
52
|
|
* @throws IOException if the file cannot be read |
|
53
|
|
*/ |
|
54
|
|
public static boolean looksLikeJsonLines(Path file) throws IOException { |
|
55
|
|
for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) { |
|
56
|
|
String trimmed = line.strip(); |
|
57
|
2
1. looksLikeJsonLines : removed conditional - replaced equality check with true → SURVIVED
2. looksLikeJsonLines : removed conditional - replaced equality check with false → KILLED
|
if (!trimmed.isEmpty()) { |
|
58
|
3
1. looksLikeJsonLines : removed conditional - replaced equality check with true → KILLED
2. looksLikeJsonLines : removed conditional - replaced equality check with false → KILLED
3. looksLikeJsonLines : replaced boolean return with true for org/egothor/methodatlas/AiCacheStore::looksLikeJsonLines → KILLED
|
return trimmed.charAt(0) == '{'; |
|
59
|
|
} |
|
60
|
|
} |
|
61
|
1
1. looksLikeJsonLines : replaced boolean return with true for org/egothor/methodatlas/AiCacheStore::looksLikeJsonLines → NO_COVERAGE
|
return false; |
|
62
|
|
} |
|
63
|
|
|
|
64
|
|
/** |
|
65
|
|
* Reads all cache entries from a JSON-Lines file. Blank lines are ignored and a |
|
66
|
|
* single unparseable line is skipped (logged at {@code FINE}) so a partially |
|
67
|
|
* corrupt cache never aborts a scan. |
|
68
|
|
* |
|
69
|
|
* @param file path to the cache file; never {@code null} |
|
70
|
|
* @return the parsed entries in file order; never {@code null}; may be empty |
|
71
|
|
* @throws IOException if the file cannot be read |
|
72
|
|
*/ |
|
73
|
|
public static List<AiCacheEntry> read(Path file) throws IOException { |
|
74
|
|
List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8); |
|
75
|
|
List<AiCacheEntry> entries = new ArrayList<>(lines.size()); |
|
76
|
|
for (String line : lines) { |
|
77
|
|
String trimmed = line.strip(); |
|
78
|
2
1. read : removed conditional - replaced equality check with false → SURVIVED
2. read : removed conditional - replaced equality check with true → KILLED
|
if (trimmed.isEmpty()) { |
|
79
|
|
continue; |
|
80
|
|
} |
|
81
|
|
try { |
|
82
|
|
entries.add(MAPPER.readValue(trimmed, AiCacheEntry.class)); |
|
83
|
|
} catch (JacksonException e) { |
|
84
|
|
LOG.log(Level.FINE, e, () -> "Skipping unparseable AI cache line"); |
|
85
|
|
} |
|
86
|
|
} |
|
87
|
1
1. read : replaced return value with Collections.emptyList for org/egothor/methodatlas/AiCacheStore::read → KILLED
|
return entries; |
|
88
|
|
} |
|
89
|
|
|
|
90
|
|
/** |
|
91
|
|
* Writes cache entries to a JSON-Lines file, one entry per line, overwriting any |
|
92
|
|
* existing file. Parent directories are created if absent. |
|
93
|
|
* |
|
94
|
|
* @param file destination path; never {@code null} |
|
95
|
|
* @param entries entries to write; never {@code null} |
|
96
|
|
* @throws IOException if the file cannot be written |
|
97
|
|
*/ |
|
98
|
|
public static void write(Path file, Collection<AiCacheEntry> entries) throws IOException { |
|
99
|
|
Path parent = file.toAbsolutePath().getParent(); |
|
100
|
2
1. write : removed conditional - replaced equality check with true → SURVIVED
2. write : removed conditional - replaced equality check with false → SURVIVED
|
if (parent != null) { |
|
101
|
|
Files.createDirectories(parent); |
|
102
|
|
} |
|
103
|
|
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { |
|
104
|
|
for (AiCacheEntry entry : entries) { |
|
105
|
1
1. write : removed call to java/io/BufferedWriter::write → KILLED
|
writer.write(MAPPER.writeValueAsString(entry)); |
|
106
|
1
1. write : removed call to java/io/BufferedWriter::write → SURVIVED
|
writer.write('\n'); |
|
107
|
|
} |
|
108
|
|
} |
|
109
|
|
} |
|
110
|
|
} |
| | Mutations |
| 57 |
|
1.1 Location : looksLikeJsonLines Killed by : none removed conditional - replaced equality check with true → SURVIVED
Covering tests
Covered by tests:
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_csvWithoutAiColumns_producesInactiveCache(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_csvWithoutContentHashColumn_producesInactiveCache(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:lookup_nullHash_returnsMissAndIncrementsMisses(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_multipleMethodsSameHash_groupedIntoOneSuggestion(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_nonSecurityMethod_cachedAndRestoredAsNonSecurity(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:legacyCsvEntry_servesClassificationNotVerdicts(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_methodSuggestionFieldsRestoredCorrectly(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_csvWithContentHashAndAiColumns_hitOnMatchingHash(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withMatchingSignatureAndVerdicts_servesBothWithoutAnyEngineCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withoutVerdicts_issuesExactlyOneDedicatedTriageCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheEntryFromDifferentPromptCatalogue_isNotReused(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_signatureMismatchMissesBoth(java.nio.file.Path)]
- org.egothor.methodatlas.MethodAtlasAppAiTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppAiTest]/[method:aiCache_miss_engineCalledForUnknownHash(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:lookup_unknownHash_returnsMiss(java.nio.file.Path)]
- org.egothor.methodatlas.MethodAtlasAppAiTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppAiTest]/[method:aiCache_hit_engineNotCalledForUnchangedClass(java.nio.file.Path)]
2.2 Location : looksLikeJsonLines Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)] removed conditional - replaced equality check with false → KILLED
|
| 58 |
|
1.1 Location : looksLikeJsonLines Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_multipleMethodsSameHash_groupedIntoOneSuggestion(java.nio.file.Path)] removed conditional - replaced equality check with true → KILLED
2.2 Location : looksLikeJsonLines Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)] removed conditional - replaced equality check with false → KILLED
3.3 Location : looksLikeJsonLines Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:load_multipleMethodsSameHash_groupedIntoOneSuggestion(java.nio.file.Path)] replaced boolean return with true for org/egothor/methodatlas/AiCacheStore::looksLikeJsonLines → KILLED
|
| 61 |
|
1.1 Location : looksLikeJsonLines Killed by : none replaced boolean return with true for org/egothor/methodatlas/AiCacheStore::looksLikeJsonLines → NO_COVERAGE
|
| 78 |
|
1.1 Location : read Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)] removed conditional - replaced equality check with true → KILLED
2.2 Location : read Killed by : none removed conditional - replaced equality check with false → SURVIVED
Covering tests
Covered by tests:
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withMatchingSignatureAndVerdicts_servesBothWithoutAnyEngineCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withoutVerdicts_issuesExactlyOneDedicatedTriageCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheEntryFromDifferentPromptCatalogue_isNotReused(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_signatureMismatchMissesBoth(java.nio.file.Path)]
|
| 87 |
|
1.1 Location : read Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)] replaced return value with Collections.emptyList for org/egothor/methodatlas/AiCacheStore::read → KILLED
|
| 100 |
|
1.1 Location : write Killed by : none removed conditional - replaced equality check with true → SURVIVED
Covering tests
Covered by tests:
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withMatchingSignatureAndVerdicts_servesBothWithoutAnyEngineCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withoutVerdicts_issuesExactlyOneDedicatedTriageCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheEntryFromDifferentPromptCatalogue_isNotReused(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_signatureMismatchMissesBoth(java.nio.file.Path)]
2.2 Location : write Killed by : none removed conditional - replaced equality check with false → SURVIVED
Covering tests
Covered by tests:
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withMatchingSignatureAndVerdicts_servesBothWithoutAnyEngineCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withoutVerdicts_issuesExactlyOneDedicatedTriageCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheEntryFromDifferentPromptCatalogue_isNotReused(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_signatureMismatchMissesBoth(java.nio.file.Path)]
|
| 105 |
|
1.1 Location : write Killed by : org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)] removed call to java/io/BufferedWriter::write → KILLED
|
| 106 |
|
1.1 Location : write Killed by : none removed call to java/io/BufferedWriter::write → SURVIVED
Covering tests
Covered by tests:
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_roundTripsClassificationAndVerdicts(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withMatchingSignatureAndVerdicts_servesBothWithoutAnyEngineCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheHit_withoutVerdicts_issuesExactlyOneDedicatedTriageCall(java.nio.file.Path)]
- org.egothor.methodatlas.command.ScanOrchestratorCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorCacheTest]/[method:cacheEntryFromDifferentPromptCatalogue_isNotReused(java.nio.file.Path)]
- org.egothor.methodatlas.AiResultCacheTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.AiResultCacheTest]/[method:unifiedCache_signatureMismatchMissesBoth(java.nio.file.Path)]
|