From 58d55d945a6b8b33793be456afb5b25b4f8948b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Sat, 18 Jul 2026 14:12:10 +0200 Subject: [PATCH] feat(config builder): add support for inherited config fields add support for configuration fields declared in superclasses, which were previously ignored by the config builder. clean up code across the file: - remove unused java.util.Arrays and java.util.stream.Collectors imports - fix inconsistent indentation throughout ConfigBuilder.java - refactor the participates helper method to accept an allOptions parameter - rewrite the getChildren method to loop over the full class hierarchy instead of using streams - add support for per-class Configuration annotations to set parent key and allOptions flag per class --- .../darkbot/config/tree/ConfigBuilder.java | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigBuilder.java b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigBuilder.java index 3cd6054df..487644ff2 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigBuilder.java +++ b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigBuilder.java @@ -12,15 +12,14 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; /** * Class responsible for building a {@link ConfigSetting} tree from a class - * This class does NOT fill in any details about the data present in the configuration, + * This class does NOT fill in any details about the data present in the + * configuration, * only creates the containers for it. */ public class ConfigBuilder implements API.Singleton { @@ -29,7 +28,7 @@ public class ConfigBuilder implements API.Singleton { private final SettingHandlerFactory settingHandlerFactory; public ConfigBuilder(I18nAPI i18n, - SettingHandlerFactory settingHandlerFactory) { + SettingHandlerFactory settingHandlerFactory) { this.i18n = i18n; this.settingHandlerFactory = settingHandlerFactory; } @@ -43,8 +42,8 @@ public ConfigSetting.Parent of(Class type, String rootName, @Nullable baseKey = cfg.value(); allOptions = cfg.allOptions(); } else { - com.github.manolo8.darkbot.config.types.Option legacyOption = - type.getAnnotation(com.github.manolo8.darkbot.config.types.Option.class); + com.github.manolo8.darkbot.config.types.Option legacyOption = type + .getAnnotation(com.github.manolo8.darkbot.config.types.Option.class); if (legacyOption != null && !legacyOption.key().isEmpty()) baseKey = legacyOption.key(); } @@ -58,8 +57,8 @@ private class Builder { private final boolean allConfig; public Builder(PluginInfo namespace, - String baseKey, - boolean allConfig) { + String baseKey, + boolean allConfig) { this.namespace = namespace; this.baseKey = baseKey; this.allConfig = allConfig; @@ -76,16 +75,23 @@ public ConfigSettingImpl.Root build(Class type, String rootName) { } private Map> getChildren(ConfigSetting.Parent p, Class type) { - Configuration cfg = type.getAnnotation(Configuration.class); - String parentKey = cfg != null ? cfg.value() : p.getKey(); - - return Arrays.stream(type.getDeclaredFields()) - .filter(this::participates) - .collect(Collectors.toMap( - f -> f.getName().toLowerCase(Locale.ROOT), - f -> createConfig(p, parentKey, f), - (a, b) -> a, - LinkedHashMap::new)); + Map> children = new LinkedHashMap<>(); + // Walk up the class hierarchy so fields inherited from parent classes + // are also included. Each class may have its own @Configuration that + // determines its parentKey and allOptions. + for (Class current = type; current != null + && current != Object.class; current = current.getSuperclass()) { + Configuration cfg = current.getAnnotation(Configuration.class); + String parentKey = cfg != null ? cfg.value() : p.getKey(); + boolean allOptions = cfg != null ? cfg.allOptions() : allConfig; + for (Field field : current.getDeclaredFields()) { + if (!participates(field, allOptions)) + continue; + children.putIfAbsent(field.getName().toLowerCase(Locale.ROOT), + createConfig(p, parentKey, field)); + } + } + return children; } private ConfigSetting createConfig(ConfigSetting.Parent parent, String parentKey, Field field) { @@ -94,24 +100,25 @@ private ConfigSetting createConfig(ConfigSetting.Parent parent, String par String key = parentKey + "." + field.getName().toLowerCase(Locale.ROOT); String name, description; - com.github.manolo8.darkbot.config.types.Option legacyOption - = field.getAnnotation(com.github.manolo8.darkbot.config.types.Option.class); + com.github.manolo8.darkbot.config.types.Option legacyOption = field + .getAnnotation(com.github.manolo8.darkbot.config.types.Option.class); if (legacyOption != null) { - if (!legacyOption.key().isEmpty()) key = legacyOption.key(); + if (!legacyOption.key().isEmpty()) + key = legacyOption.key(); name = i18n.getOrDefault(namespace, key, legacyOption.value()); description = i18n.getOrDefault(namespace, key + ".desc", legacyOption.description().isEmpty() ? null : legacyOption.description()); } else { Option option = field.getAnnotation(Option.class); - if (option != null && !option.value().isEmpty()) key = option.value(); + if (option != null && !option.value().isEmpty()) + key = option.value(); name = i18n.getOrDefault(namespace, key, ""); description = i18n.getOrDefault(namespace, key + ".desc", null); } - // If we know for sure it is a leaf, we ignore trying to make an intermediate // If the intermediate turns out not to have any children, discard it // and go back to it being a leaf node @@ -129,21 +136,29 @@ private ConfigSetting createConfig(ConfigSetting.Parent parent, String par } /** - * @param field the field to check - * @return true if this field participates in the configuration tree, false otherwise + * @param field the field to check + * @param allOptions true if all non-static public fields should be options + * @return true if this field participates in the configuration tree, false + * otherwise */ - private boolean participates(Field field) { - if (!Modifier.isPublic(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) return false; - if (field.isAnnotationPresent(com.github.manolo8.darkbot.config.types.Option.class)) return true; - if (field.isAnnotationPresent(Option.Ignore.class)) return false; - if (field.isAnnotationPresent(Option.class)) return true; - if (Modifier.isTransient(field.getModifiers())) return false; - return allConfig; + private boolean participates(Field field, boolean allOptions) { + if (!Modifier.isPublic(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) + return false; + if (field.isAnnotationPresent(com.github.manolo8.darkbot.config.types.Option.class)) + return true; + if (field.isAnnotationPresent(Option.Ignore.class)) + return false; + if (field.isAnnotationPresent(Option.class)) + return true; + if (Modifier.isTransient(field.getModifiers())) + return false; + return allOptions; } /** * @param field the field to check - * @return true if this is a leaf node, no more children under this, false otherwise + * @return true if this is a leaf node, no more children under this, false + * otherwise */ private boolean isLeaf(Field field) { Class type = field.getType();