Files
schemaCheck/src/main/java/com/codechecker/cache/config/ConfigLoader.java
dongzi 110beb79c0
All checks were successful
缓存序列化结构检查 / cache-schema-check (push) Has been skipped
feat: 项目整体命名修改cache-schema-checker
2026-07-14 11:03:34 +08:00

211 lines
8.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.codechecker.cache.config;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 双层配置加载器:先读取 jar 内 {@code default-config.yaml},再与业务仓库配置深度合并(业务配置优先)。
*/
public final class ConfigLoader {
private static final String DEFAULT_CONFIG_RESOURCE = "default-config.yaml";
private ConfigLoader() {
}
public static CheckerConfig load(Path businessConfigPath) {
Map<String, Object> merged = loadDefault();
if (businessConfigPath != null && Files.exists(businessConfigPath)) {
Map<String, Object> business = loadYaml(businessConfigPath);
merged = deepMerge(merged, business);
}
return bind(merged);
}
@SuppressWarnings("unchecked")
static Map<String, Object> loadDefault() {
try (InputStream in = ConfigLoader.class.getClassLoader().getResourceAsStream(DEFAULT_CONFIG_RESOURCE)) {
if (in == null) {
throw new IllegalStateException("jar 内缺少 default-config.yaml");
}
Object obj = new Yaml().load(in);
return obj == null ? new LinkedHashMap<>() : (Map<String, Object>) obj;
} catch (IOException e) {
throw new IllegalStateException("读取默认配置失败", e);
}
}
@SuppressWarnings("unchecked")
static Map<String, Object> loadYaml(Path path) {
try (InputStream in = Files.newInputStream(path)) {
Object obj = new Yaml().load(in);
return obj == null ? new LinkedHashMap<>() : (Map<String, Object>) obj;
} catch (IOException e) {
throw new IllegalStateException("读取业务配置失败: " + path, e);
}
}
@SuppressWarnings("unchecked")
static Map<String, Object> deepMerge(Map<String, Object> base, Map<String, Object> override) {
Map<String, Object> result = new LinkedHashMap<>(base);
for (Map.Entry<String, Object> entry : override.entrySet()) {
String key = entry.getKey();
Object overrideValue = entry.getValue();
Object baseValue = result.get(key);
if (baseValue instanceof Map && overrideValue instanceof Map) {
result.put(key, deepMerge((Map<String, Object>) baseValue, (Map<String, Object>) overrideValue));
} else {
// 标量、列表:业务配置直接覆盖
result.put(key, overrideValue);
}
}
return result;
}
@SuppressWarnings("unchecked")
private static CheckerConfig bind(Map<String, Object> map) {
CheckerConfig config = new CheckerConfig();
config.setEnabled(bool(map, "enabled", true));
config.setMode(str(map, "mode", "notify"));
config.setScanTestSources(bool(map, "scan_test_sources", false));
config.setSourceRoots(strList(map.get("source_roots")));
config.setIncludeModules(strList(map.get("include_modules")));
config.setExcludeModules(strList(map.get("exclude_modules")));
Map<String, Object> notify = asMap(map.get("notify"));
CheckerConfig.Notify n = config.getNotify();
n.setEnabled(bool(notify, "enabled", true));
n.setWebhookUrl(resolveWebhookUrl(notify));
n.setNotifyOnClean(bool(notify, "notify_on_clean", false));
n.setTitlePrefix(str(notify, "title_prefix", "[缓存结构变更]"));
Map<String, Object> ignore = asMap(map.get("ignore"));
CheckerConfig.Ignore ig = config.getIgnore();
ig.setKeyPatterns(strList(ignore.get("key_patterns")));
ig.setFilePatterns(strList(ignore.get("file_patterns")));
ig.setWriterMethods(strList(ignore.get("writer_methods")));
Map<String, Object> detection = asMap(map.get("detection"));
CheckerConfig.Detection d = config.getDetection();
d.setPatterns(strList(detection.get("patterns")));
d.setMinConfidence(dbl(detection, "min_confidence", 0.6));
d.setMaxFieldDepth((int) lng(detection, "max_field_depth", 8));
Map<String, Object> severityOverrides = asMap(map.get("severity_overrides"));
Map<String, String> so = new LinkedHashMap<>();
for (Map.Entry<String, Object> e : severityOverrides.entrySet()) {
so.put(e.getKey(), String.valueOf(e.getValue()));
}
config.setSeverityOverrides(so);
List<CheckerConfig.ManualMapping> mappings = new ArrayList<>();
for (Object item : asList(map.get("manual_mappings"))) {
Map<String, Object> m = asMap(item);
CheckerConfig.ManualMapping mm = new CheckerConfig.ManualMapping();
mm.setId(str(m, "id", null));
mm.setWriterMethod(str(m, "writer_method", null));
mm.setKeyPattern(str(m, "key_pattern", null));
mm.setValueType(str(m, "value_type", null));
mm.setDescription(str(m, "description", null));
mappings.add(mm);
}
config.setManualMappings(mappings);
List<CheckerConfig.Suppression> suppressions = new ArrayList<>();
for (Object item : asList(map.get("suppressions"))) {
Map<String, Object> m = asMap(item);
CheckerConfig.Suppression sp = new CheckerConfig.Suppression();
sp.setId(str(m, "id", null));
sp.setWriterMethod(str(m, "writer_method", null));
sp.setKeyPattern(str(m, "key_pattern", null));
sp.setChangeTypes(strList(m.get("change_types")));
sp.setReason(str(m, "reason", null));
suppressions.add(sp);
}
config.setSuppressions(suppressions);
return config;
}
/**
* 优先读取 webhook_url兼容旧字段 webhook_env值为 http 开头时视为 URL
*/
private static String resolveWebhookUrl(Map<String, Object> notify) {
String url = str(notify, "webhook_url", "");
if (url != null && !url.trim().isEmpty()) {
return url.trim();
}
String legacy = str(notify, "webhook_env", "");
if (legacy != null && legacy.trim().startsWith("http")) {
return legacy.trim();
}
return "";
}
@SuppressWarnings("unchecked")
private static Map<String, Object> asMap(Object obj) {
if (obj instanceof Map) {
return (Map<String, Object>) obj;
}
return new LinkedHashMap<>();
}
private static List<Object> asList(Object obj) {
if (obj instanceof List) {
return (List<Object>) obj;
}
return new ArrayList<>();
}
private static List<String> strList(Object obj) {
List<String> result = new ArrayList<>();
if (obj instanceof List) {
for (Object o : (List<?>) obj) {
if (o != null) {
result.add(String.valueOf(o));
}
}
}
return result;
}
private static String str(Map<String, Object> map, String key, String def) {
Object v = map.get(key);
return v == null ? def : String.valueOf(v);
}
private static boolean bool(Map<String, Object> map, String key, boolean def) {
Object v = map.get(key);
if (v instanceof Boolean) {
return (Boolean) v;
}
return v == null ? def : Boolean.parseBoolean(String.valueOf(v));
}
private static double dbl(Map<String, Object> map, String key, double def) {
Object v = map.get(key);
if (v instanceof Number) {
return ((Number) v).doubleValue();
}
return v == null ? def : Double.parseDouble(String.valueOf(v));
}
private static long lng(Map<String, Object> map, String key, long def) {
Object v = map.get(key);
if (v instanceof Number) {
return ((Number) v).longValue();
}
return v == null ? def : Long.parseLong(String.valueOf(v));
}
}