From 62f35bdb785411500e04ac7c19b9e29e2696214d Mon Sep 17 00:00:00 2001 From: Timi Date: Sat, 9 May 2026 14:22:44 +0800 Subject: [PATCH] v0.0.3 --- pom.xml | 42 ++++- .../java/com/imyeyu/config/ConfigLoader.java | 163 ++++++++++++++---- 2 files changed, 166 insertions(+), 39 deletions(-) diff --git a/pom.xml b/pom.xml index b59f744..94a91c4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.imyeyu.config timi-config - 0.0.2 + 0.0.3 true @@ -27,10 +27,42 @@ maven-source-plugin 3.3.1 + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + ${project.basedir}/src/main/java + ${project.build.directory}/delombok + false + UTF-8 + + + + generate-sources + + delombok + + + + + + org.projectlombok + lombok + 1.18.36 + + + org.apache.maven.plugins maven-javadoc-plugin 3.11.2 + + ${project.build.directory}/delombok + UTF-8 + UTF-8 + UTF-8 + @@ -59,23 +91,23 @@ org.yaml snakeyaml - 2.2 + 2.6 com.imyeyu.io timi-io - 0.0.2 + 0.0.5 org.junit.jupiter junit-jupiter - 5.10.3 + 6.0.3 test org.projectlombok lombok - 1.18.34 + 1.18.46 test diff --git a/src/main/java/com/imyeyu/config/ConfigLoader.java b/src/main/java/com/imyeyu/config/ConfigLoader.java index c8b017e..562e0f6 100644 --- a/src/main/java/com/imyeyu/config/ConfigLoader.java +++ b/src/main/java/com/imyeyu/config/ConfigLoader.java @@ -11,8 +11,12 @@ import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.representer.Representer; import java.io.File; +import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -40,6 +44,9 @@ public class ConfigLoader { /** * 创建配置加载器,源路径和目标路径相同 + * + * @param path 源路径/目标路径 + * @param clazz 配置类型 */ public ConfigLoader(String path, Class clazz) { this(path, path, clazz); @@ -69,7 +76,7 @@ public class ConfigLoader { LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowRecursiveKeys(false); // 禁止递归键 loaderOptions.setMaxAliasesForCollections(50); // 限制别名数量 - loaderOptions.setCodePointLimit((int) (IOSize.MB * 3)); // 限制 3 MB + loaderOptions.setCodePointLimit((int) (IOSize.MB * 10)); // 限制 10 MB DumperOptions dumperOptions = new DumperOptions(); dumperOptions.setIndent(4); @@ -96,13 +103,7 @@ public class ConfigLoader { 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); + Object raw = rawYaml.load(content); if (raw == null) { value = null; return null; @@ -129,9 +130,8 @@ public class ConfigLoader { Object toSerialize = applyConvertersOnDump(value); // SnakeYAML 序列化 String yamlContent = yaml.dump(toSerialize); - // 空格转制表符 - String toWrite = yamlContent.replaceAll("( ){4}", " "); - IO.toFile(file, toWrite); + + IO.toFile(file, yamlContent); } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "dump config error: " + file.getAbsolutePath(), e); } @@ -163,11 +163,10 @@ public class ConfigLoader { Class currentClass = target.getClass(); while (currentClass != null && currentClass != Object.class) { for (Field field : currentClass.getDeclaredFields()) { - String fieldName = field.getName(); - if (!map.containsKey(fieldName)) { + Object rawValue = resolveMapValue(map, field.getName()); + if (rawValue == null && !containsFieldKey(map, field.getName())) { continue; } - Object rawValue = map.get(fieldName); Object converted = convertFieldValue(field, rawValue); field.setAccessible(true); field.set(target, converted); @@ -176,6 +175,17 @@ public class ConfigLoader { } } + private boolean containsFieldKey(Map map, String fieldName) { + return map.containsKey(fieldName) || map.containsKey(toSnakeCase(fieldName)); + } + + private Object resolveMapValue(Map map, String fieldName) { + if (map.containsKey(fieldName)) { + return map.get(fieldName); + } + return map.get(toSnakeCase(fieldName)); + } + private Object convertFieldValue(Field field, Object rawValue) { BaseConverter converter = converters.get(field.getType()); if (converter != null) { @@ -188,12 +198,40 @@ public class ConfigLoader { if (fieldType.isEnum()) { return toEnumValue(fieldType, rawValue.toString()); } + if (fieldType.isArray() && rawValue instanceof Iterable rawIterable) { + return toArrayValue(fieldType.getComponentType(), rawIterable); + } if (rawValue instanceof Map rawMap && shouldMapToBean(fieldType)) { return mapToBean(rawMap, fieldType); } return convertScalarValue(fieldType, rawValue); } + private Object toArrayValue(Class componentType, Iterable rawIterable) { + List values = new ArrayList<>(); + for (Object rawElement : rawIterable) { + values.add(convertArrayElement(componentType, rawElement)); + } + Object array = Array.newInstance(componentType, values.size()); + for (int i = 0; i < values.size(); i++) { + Array.set(array, i, values.get(i)); + } + return array; + } + + private Object convertArrayElement(Class componentType, Object rawElement) { + if (rawElement == null) { + return null; + } + if (componentType.isEnum()) { + return toEnumValue(componentType, rawElement.toString()); + } + if (rawElement instanceof Map rawMap && shouldMapToBean(componentType)) { + return mapToBean(rawMap, componentType); + } + return convertScalarValue(componentType, rawElement); + } + private boolean shouldMapToBean(Class fieldType) { if (fieldType.isPrimitive() || fieldType.isEnum()) { return false; @@ -251,35 +289,92 @@ public class ConfigLoader { } /** 序列化时应用转换器 */ - @SuppressWarnings("unchecked") - private K applyConvertersOnDump(K obj) { - if (obj == null || converters.isEmpty()) { - return obj; + private Object applyConvertersOnDump(Object obj) { + return toYamlValue(null, obj); + } + + private Object toYamlValue(Field field, Object value) { + if (field != null) { + BaseConverter converter = converters.get(field.getType()); + if (converter != null) { + return converter.serialize0(field, value); + } + } + if (value == null) { + return null; + } + Class valueType = value.getClass(); + if (valueType.isEnum()) { + return ((Enum) value).name(); + } + if (isScalarType(valueType)) { + return value; + } + if (valueType.isArray()) { + int length = Array.getLength(value); + List result = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + result.add(toYamlValue(null, Array.get(value, i))); + } + return result; + } + if (value instanceof Iterable iterable) { + List result = new ArrayList<>(); + for (Object item : iterable) { + result.add(toYamlValue(null, item)); + } + return result; + } + if (value instanceof Map mapValue) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : mapValue.entrySet()) { + result.put(entry.getKey(), toYamlValue(null, entry.getValue())); + } + return result; + } + if (!shouldMapToBean(valueType)) { + return value; } try { - // 创建副本 - K copy = (K) obj.getClass().getDeclaredConstructor().newInstance(); - Class currentClass = obj.getClass(); + Map result = new LinkedHashMap<>(); + Class currentClass = valueType; 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); + for (Field declaredField : currentClass.getDeclaredFields()) { + declaredField.setAccessible(true); + Object fieldValue = toYamlValue(declaredField, declaredField.get(value)); + if (fieldValue != null) { + result.put(toSnakeCase(declaredField.getName()), fieldValue); } } currentClass = currentClass.getSuperclass(); } - return copy; + return result; } catch (Exception e) { throw new TimiException(TimiCode.ERROR, "converter error on dump", e); } } + + private boolean isScalarType(Class type) { + return type.isPrimitive() + || type == String.class + || Number.class.isAssignableFrom(type) + || type == Boolean.class + || type == Character.class; + } + + private String toSnakeCase(String fieldName) { + StringBuilder builder = new StringBuilder(fieldName.length() + 4); + for (int i = 0; i < fieldName.length(); i++) { + char current = fieldName.charAt(i); + if (Character.isUpperCase(current)) { + if (i != 0) { + builder.append('_'); + } + builder.append(Character.toLowerCase(current)); + continue; + } + builder.append(current); + } + return builder.toString(); + } }