KnownRulesStore.java
/*******************************************************************************
* Copyright (c) 2025 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.mining.core.config;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.sandbox.jdt.triggerpattern.llm.CommitEvaluation;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Read-compatible archive for the legacy {@code known-rules.json} format.
*
* <p>This store is no longer an approval registry, duplicate index, or LLM
* prompt source. New proposals are represented exclusively by versioned
* {@code MiningCandidate} JSON. The class remains temporarily so historic
* reports can be loaded and archived without data loss.</p>
*
* @deprecated use staged candidates and their rule fingerprints
*/
@Deprecated(forRemoval = true)
public class KnownRulesStore {
/** Legacy status retained for JSON compatibility. */
public enum RuleStatus {
DISCOVERED,
IMPLEMENTED,
REJECTED,
NEEDS_DSL_EXTENSION
}
/** Legacy known-rule record retained for archival reads. */
public static class KnownRule {
private String id;
private String category;
private String dslRule;
private String summary;
private String discoveredAt;
private int discoveredInRun;
private String sourceCommit;
private String sourceRepo;
private String candidateId;
private RuleStatus status;
private String hintFile;
public KnownRule() {
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public String getDslRule() { return dslRule; }
public void setDslRule(String dslRule) { this.dslRule = dslRule; }
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getDiscoveredAt() { return discoveredAt; }
public void setDiscoveredAt(String discoveredAt) { this.discoveredAt = discoveredAt; }
public int getDiscoveredInRun() { return discoveredInRun; }
public void setDiscoveredInRun(int discoveredInRun) { this.discoveredInRun = discoveredInRun; }
public String getSourceCommit() { return sourceCommit; }
public void setSourceCommit(String sourceCommit) { this.sourceCommit = sourceCommit; }
public String getSourceRepo() { return sourceRepo; }
public void setSourceRepo(String sourceRepo) { this.sourceRepo = sourceRepo; }
public String getCandidateId() { return candidateId; }
public void setCandidateId(String candidateId) { this.candidateId = candidateId; }
public RuleStatus getStatus() { return status; }
public void setStatus(RuleStatus status) { this.status = status; }
public String getHintFile() { return hintFile; }
public void setHintFile(String hintFile) { this.hintFile = hintFile; }
}
static class KnownRulesData {
int version = 1;
List<KnownRule> rules = new ArrayList<>();
}
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private final KnownRulesData data;
public KnownRulesStore() {
this(new KnownRulesData());
}
private KnownRulesStore(KnownRulesData data) {
this.data = data;
if (this.data.rules == null) {
this.data.rules = new ArrayList<>();
}
}
/** Loads historic data, returning an empty archive for absent/invalid files. */
public static KnownRulesStore load(Path path) throws IOException {
if (!Files.exists(path)) {
return new KnownRulesStore();
}
try {
KnownRulesData loaded = GSON.fromJson(
Files.readString(path, StandardCharsets.UTF_8), KnownRulesData.class);
return new KnownRulesStore(loaded == null ? new KnownRulesData() : loaded);
} catch (RuntimeException e) {
System.err.println("Warning: could not load legacy known-rules.json: " //$NON-NLS-1$
+ e.getMessage());
return new KnownRulesStore();
}
}
/** Writes the archive without interpreting or promoting its contents. */
public void save(Path path) throws IOException {
Path parent = path.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Path fileName = path.getFileName();
if (fileName == null) {
throw new IOException("Cannot save legacy known rules to a root path"); //$NON-NLS-1$
}
Path temporary = path.resolveSibling(fileName + ".tmp"); //$NON-NLS-1$
Files.writeString(temporary, GSON.toJson(data) + System.lineSeparator(),
StandardCharsets.UTF_8);
try {
Files.move(temporary, path, StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(temporary, path, StandardCopyOption.REPLACE_EXISTING);
}
}
public List<KnownRule> getRules() {
return List.copyOf(data.rules);
}
public int size() {
return data.rules.size();
}
public boolean containsCommit(String commitHash) {
return data.rules.stream().anyMatch(rule -> commitHash != null
&& commitHash.equals(rule.sourceCommit));
}
/**
* New evaluations are deliberately not copied into this legacy store.
*
* @return always {@code 0}
*/
public int registerFromEvaluations(List<CommitEvaluation> evaluations, int runNumber) {
return 0;
}
/**
* Legacy rule data is deliberately excluded from LLM prompts.
*
* @return always the empty string
*/
public String formatForPrompt() {
return ""; //$NON-NLS-1$
}
/** Returns a read-only index for archival tooling only. */
public Map<String, String> getCommitHashIndex() {
Map<String, String> index = new LinkedHashMap<>();
for (KnownRule rule : data.rules) {
if (rule.sourceCommit != null) {
index.put(rule.sourceCommit, rule.id);
}
}
return Map.copyOf(index);
}
}