package com.imyeyu.config; import com.imyeyu.io.IO; import com.imyeyu.io.IOSize; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.representer.Representer; import java.io.File; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; /** * 配置加载器 - SnakeYAML 封装 * * * @author 夜雨 * @since 2026-01-12 12:03 */ public class ConfigLoader { private final File file; private final Yaml yaml; private final Yaml rawYaml; private final Class clazz; private final Map, BaseConverter> converters = new HashMap<>(); private T value; /** * 创建配置加载器,源路径和目标路径相同 */ public ConfigLoader(String path, Class clazz) { this(path, path, clazz); } /** * 创建配置加载器 * * @param srcPath 默认配置文件路径,通常为 classpath 资源 * @param distPath 配置文件路径 * @param clazz 配置类型 */ public ConfigLoader(String srcPath, String distPath, Class clazz) { this.file = new File(distPath); this.clazz = clazz; // 如果配置文件不存在则复制默认配置 if (!file.exists()) { try { IO.resourceToDisk(getClass(), srcPath, distPath); } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "load default config error", e); } } // 配置 SnakeYAML LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowRecursiveKeys(false); // 禁止递归键 loaderOptions.setMaxAliasesForCollections(50); // 限制别名数量 loaderOptions.setCodePointLimit((int) (IOSize.MB * 3)); // 限制 3 MB DumperOptions dumperOptions = new DumperOptions(); dumperOptions.setIndent(4); dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); dumperOptions.setIndicatorIndent(4); dumperOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN); dumperOptions.setIndentWithIndicator(true); // 使用自定义 Representer,跳过 null 值以减少配置文件体积 Representer representer = new ConfigRepresenter(dumperOptions); // 根据类型创建 Constructor Constructor constructor; if (clazz != null && !clazz.equals(Object.class)) { constructor = new Constructor(clazz, loaderOptions); } else { constructor = new Constructor(loaderOptions); } this.yaml = new Yaml(constructor, representer, dumperOptions); this.rawYaml = new Yaml(new Constructor(loaderOptions)); } /** 加载配置 */ @SuppressWarnings("unchecked") public T load() { try { String content = IO.toString(file); String normalized = content.replaceAll("\t", " "); if (converters.isEmpty()) { // SnakeYAML 自动转换 - 使用指定类型构造 value = yaml.load(normalized); return value; } Object raw = rawYaml.load(normalized); if (raw == null) { value = null; return null; } if (clazz == null || clazz.equals(Object.class)) { value = (T) raw; return value; } if (raw instanceof Map rawMap) { value = mapToBean(rawMap, clazz); return value; } value = (T) raw; return value; } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "load config error: " + file.getAbsolutePath(), e); } } /** 保存配置 */ public void dump() { try { // 应用序列化转换器 Object toSerialize = applyConvertersOnDump(value); // SnakeYAML 序列化 String yamlContent = yaml.dump(toSerialize); // 空格转制表符 String toWrite = yamlContent.replaceAll("( ){4}", " "); IO.toFile(file, toWrite); } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "dump config error: " + file.getAbsolutePath(), e); } } /** * 添加自定义类型转换器 *

* 用于 YAML 不支持的类型序列化和反序列化,如 JavaFX Property 等 * * @param type 字段类型 * @param converter 转换器 */ public void addConverter(Class type, BaseConverter converter) { converters.put(type, converter); } private K mapToBean(Map map, Class targetClass) { try { K instance = targetClass.getDeclaredConstructor().newInstance(); applyMapToObject(map, instance); return instance; } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "map to bean error: " + targetClass.getName(), e); } } private void applyMapToObject(Map map, Object target) throws IllegalAccessException { Class currentClass = target.getClass(); while (currentClass != null && currentClass != Object.class) { for (Field field : currentClass.getDeclaredFields()) { String fieldName = field.getName(); if (!map.containsKey(fieldName)) { continue; } Object rawValue = map.get(fieldName); Object converted = convertFieldValue(field, rawValue); field.setAccessible(true); field.set(target, converted); } currentClass = currentClass.getSuperclass(); } } private Object convertFieldValue(Field field, Object rawValue) { BaseConverter converter = converters.get(field.getType()); if (converter != null) { return converter.deserialize0(field, rawValue); } if (rawValue == null) { return null; } Class fieldType = field.getType(); if (fieldType.isEnum()) { return toEnumValue(fieldType, rawValue.toString()); } if (rawValue instanceof Map rawMap && shouldMapToBean(fieldType)) { return mapToBean(rawMap, fieldType); } return convertScalarValue(fieldType, rawValue); } private boolean shouldMapToBean(Class fieldType) { if (fieldType.isPrimitive() || fieldType.isEnum()) { return false; } if (fieldType.getName().startsWith("java.")) { return false; } return !fieldType.isInterface(); } private Object convertScalarValue(Class fieldType, Object rawValue) { if (fieldType.isInstance(rawValue)) { return rawValue; } String text = rawValue.toString(); if (fieldType == String.class) { return text; } if (fieldType == int.class || fieldType == Integer.class) { return rawValue instanceof Number ? ((Number) rawValue).intValue() : Integer.parseInt(text); } if (fieldType == long.class || fieldType == Long.class) { return rawValue instanceof Number ? ((Number) rawValue).longValue() : Long.parseLong(text); } if (fieldType == double.class || fieldType == Double.class) { return rawValue instanceof Number ? ((Number) rawValue).doubleValue() : Double.parseDouble(text); } if (fieldType == float.class || fieldType == Float.class) { return rawValue instanceof Number ? ((Number) rawValue).floatValue() : Float.parseFloat(text); } if (fieldType == short.class || fieldType == Short.class) { return rawValue instanceof Number ? ((Number) rawValue).shortValue() : Short.parseShort(text); } if (fieldType == byte.class || fieldType == Byte.class) { return rawValue instanceof Number ? ((Number) rawValue).byteValue() : Byte.parseByte(text); } if (fieldType == boolean.class || fieldType == Boolean.class) { return rawValue instanceof Boolean ? rawValue : Boolean.parseBoolean(text); } if (fieldType == char.class || fieldType == Character.class) { return text.isEmpty() ? null : text.charAt(0); } return rawValue; } @SuppressWarnings("unchecked") private > E toEnumValue(Class enumType, String value) { Class enumClass = (Class) enumType; for (E constant : enumClass.getEnumConstants()) { if (constant.name().equalsIgnoreCase(value)) { return constant; } } return null; } /** 序列化时应用转换器 */ @SuppressWarnings("unchecked") private K applyConvertersOnDump(K obj) { if (obj == null || converters.isEmpty()) { return obj; } try { // 创建副本 K copy = (K) obj.getClass().getDeclaredConstructor().newInstance(); Class currentClass = obj.getClass(); while (currentClass != null && currentClass != Object.class) { for (Field field : currentClass.getDeclaredFields()) { field.setAccessible(true); Object fieldValue = field.get(obj); BaseConverter converter = converters.get(field.getType()); if (converter != null) { // 应用转换器 Object yamlValue = converter.serialize0(field, fieldValue); field.set(copy, yamlValue); } else { // 直接复制 field.set(copy, fieldValue); } } currentClass = currentClass.getSuperclass(); } return copy; } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "converter error on dump", e); } } }