diff options
author | isXander <xandersmith2008@gmail.com> | 2023-08-14 23:27:45 +0100 |
---|---|---|
committer | isXander <xandersmith2008@gmail.com> | 2023-08-14 23:27:45 +0100 |
commit | b3355266572deef1a5c3e494ad162c592383e455 (patch) | |
tree | 876510a21e27d0052cb7a1501c0295a427c9dc3c /common/src/main/java/dev/isxander/yacl3/config | |
parent | d37e147dbb4db44a921533b572aed3e54b5c6a42 (diff) | |
download | YetAnotherConfigLib-b3355266572deef1a5c3e494ad162c592383e455.tar.gz YetAnotherConfigLib-b3355266572deef1a5c3e494ad162c592383e455.tar.bz2 YetAnotherConfigLib-b3355266572deef1a5c3e494ad162c592383e455.zip |
More-or-less complete API for YACL auto-gen
Diffstat (limited to 'common/src/main/java/dev/isxander/yacl3/config')
32 files changed, 687 insertions, 107 deletions
diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java index 22e471f..645a8e8 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigClassHandler.java @@ -2,6 +2,7 @@ package dev.isxander.yacl3.config.v2.api; import dev.isxander.yacl3.api.YetAnotherConfigLib; import dev.isxander.yacl3.config.v2.impl.ConfigClassHandlerImpl; +import net.minecraft.resources.ResourceLocation; import java.util.function.Function; @@ -14,6 +15,8 @@ public interface ConfigClassHandler<T> { ConfigField<?>[] fields(); + ResourceLocation id(); + YetAnotherConfigLib generateGui(); boolean supportsAutoGen(); @@ -25,6 +28,8 @@ public interface ConfigClassHandler<T> { } interface Builder<T> { + Builder<T> id(ResourceLocation id); + Builder<T> serializer(Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory); Builder<T> autoGen(boolean autoGen); diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java index 26a309f..1cd8739 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigField.java @@ -1,17 +1,17 @@ package dev.isxander.yacl3.config.v2.api; -import org.jetbrains.annotations.Nullable; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; import java.util.Optional; public interface ConfigField<T> { - String serialName(); + FieldAccess<T> access(); - Optional<String> comment(); + ReadOnlyFieldAccess<T> defaultAccess(); - FieldAccess<T> access(); + ConfigClassHandler<?> parent(); - @Nullable OptionFactory<T> factory(); + Optional<SerialField> serial(); - boolean supportsFactory(); + Optional<AutoGenField<T>> autoGen(); } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java index aed9801..a961172 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/FieldAccess.java @@ -1,14 +1,5 @@ package dev.isxander.yacl3.config.v2.api; -import java.lang.reflect.Type; - -public interface FieldAccess<T> { - T get(); - +public interface FieldAccess<T> extends ReadOnlyFieldAccess<T> { void set(T value); - - String name(); - - Type type(); - } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java index aabfcf0..9d53e79 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/OptionFactory.java @@ -1,9 +1,15 @@ package dev.isxander.yacl3.config.v2.api; import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; -public interface OptionFactory<T> { - Option<T> create(ConfigField<T> field); +import java.lang.annotation.Annotation; - Class<T> type(); +public interface OptionFactory<A extends Annotation, T> { + Option<T> createOption(A annotation, ConfigField<T> field, OptionStorage storage); + + static <A extends Annotation, T> void register(Class<A> annotationClass, OptionFactory<A, T> factory) { + OptionFactoryRegistry.registerOptionFactory(annotationClass, factory); + } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java new file mode 100644 index 0000000..5b71e58 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/ReadOnlyFieldAccess.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.lang.reflect.Type; + +public interface ReadOnlyFieldAccess<T> { + T get(); + + String name(); + + Type type(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java index 8b95c3f..e5ba001 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/ConfigEntry.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialEntry.java @@ -1,7 +1,5 @@ package dev.isxander.yacl3.config.v2.api; -import dev.isxander.yacl3.config.v2.impl.DefaultOptionFactory; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -9,10 +7,8 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface ConfigEntry { - Class<? extends OptionFactory<?>> factory() default DefaultOptionFactory.class; - - String serialName() default ""; +public @interface SerialEntry { + String value() default ""; String comment() default ""; } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java new file mode 100644 index 0000000..01c00d6 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SerialField.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.config.v2.api; + +import java.util.Optional; + +public interface SerialField { + String serialName(); + + Optional<String> comment(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java new file mode 100644 index 0000000..5c6894e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java @@ -0,0 +1,82 @@ +package dev.isxander.yacl3.config.v2.api; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.OptionDescription; +import dev.isxander.yacl3.api.OptionFlag; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.impl.FieldBackedBinding; +import net.minecraft.client.Minecraft; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Annotation; +import java.util.Set; + +public abstract class SimpleOptionFactory<A extends Annotation, T> implements OptionFactory<A, T> { + @Override + public Option<T> createOption(A annotation, ConfigField<T> field, OptionStorage storage) { + Option<T> option = Option.<T>createBuilder() + .name(this.name(annotation, field, storage)) + .description(v -> this.description(v, annotation, field, storage).build()) + .binding(new FieldBackedBinding<>(field.access(), field.defaultAccess())) + .controller(opt -> this.createController(annotation, field, storage, opt)) + .available(this.available(annotation, field, storage)) + .flags(this.flags(annotation, field, storage)) + .build(); + + postInit(annotation, field, storage, option); + return option; + } + + protected abstract ControllerBuilder<T> createController(A annotation, ConfigField<T> field, OptionStorage storage, Option<T> option); + + protected MutableComponent name(A annotation, ConfigField<T> field, OptionStorage storage) { + return Component.translatable(this.getTranslationKey(field, null)); + } + + protected OptionDescription.Builder description(T value, A annotation, ConfigField<T> field, OptionStorage storage) { + OptionDescription.Builder builder = OptionDescription.createBuilder(); + + String key = this.getTranslationKey(field, "desc"); + if (Language.getInstance().has(key)) { + builder.text(Component.translatable(key)); + } else { + key += "."; + int i = 0; + while (Language.getInstance().has(key + i++)) { + builder.text(Component.translatable(key + i)); + } + } + + String imagePath = "textures/yacl3/" + field.parent().id().getPath() + "/" + field.access().name() + ".webp"; + imagePath = imagePath.toLowerCase().replaceAll("[^a-z0-9/._:-]", "_"); + ResourceLocation imageLocation = new ResourceLocation(field.parent().id().getNamespace(), imagePath); + if (Minecraft.getInstance().getResourceManager().getResource(imageLocation).isPresent()) { + builder.webpImage(imageLocation); + } + + return builder; + } + + protected boolean available(A annotation, ConfigField<T> field, OptionStorage storage) { + return true; + } + + protected Set<OptionFlag> flags(A annotation, ConfigField<T> field, OptionStorage storage) { + return Set.of(); + } + + protected void postInit(A annotation, ConfigField<T> field, OptionStorage storage, Option<T> option) { + + } + + protected String getTranslationKey(ConfigField<T> field, @Nullable String suffix) { + String key = "yacl3.config.%s.%s".formatted(field.parent().id().toString(), field.access().name()); + if (suffix != null) key += "." + suffix; + return key; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java new file mode 100644 index 0000000..8abcb60 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGen.java @@ -0,0 +1,14 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface AutoGen { + String category(); + + String group() default ""; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java new file mode 100644 index 0000000..48db22d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/AutoGenField.java @@ -0,0 +1,9 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.util.Optional; + +public interface AutoGenField<T> { + String category(); + + Optional<String> group(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java new file mode 100644 index 0000000..bb948ac --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Boolean.java @@ -0,0 +1,21 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Boolean { + enum Formatter { + YES_NO, + TRUE_FALSE, + ON_OFF, + CUSTOM, + } + + Formatter formatter() default Formatter.TRUE_FALSE; + + boolean colored() default false; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java new file mode 100644 index 0000000..47c7b00 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/DoubleSlider.java @@ -0,0 +1,18 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DoubleSlider { + double min(); + + double max(); + + double step(); + + String format() default "%.2f"; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java new file mode 100644 index 0000000..8d1d8e5 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FloatSlider.java @@ -0,0 +1,18 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FloatSlider { + float min(); + + float max(); + + float step(); + + String format() default "%.1f"; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java new file mode 100644 index 0000000..05be857 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/IntSlider.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface IntSlider { + int min(); + + int max(); + + int step(); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java new file mode 100644 index 0000000..7a8ef22 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/Label.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Label { +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java new file mode 100644 index 0000000..67c311d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/MasterTickBox.java @@ -0,0 +1,14 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface MasterTickBox { + String[] value(); + + boolean invert() default false; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java new file mode 100644 index 0000000..f90fc29 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OptionStorage.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.api.Option; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public interface OptionStorage { + @Nullable Option<?> getOption(String fieldName); + + void scheduleOptionOperation(String fieldName, Consumer<Option<?>> optionConsumer); +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java new file mode 100644 index 0000000..413a32d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/TickBox.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface TickBox { +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java index 62aa9b6..b9274e9 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java @@ -1,24 +1,33 @@ package dev.isxander.yacl3.config.v2.impl; -import dev.isxander.yacl3.api.YetAnotherConfigLib; +import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; import dev.isxander.yacl3.platform.YACLPlatform; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.Validate; import java.lang.reflect.Constructor; import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.function.Function; public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { private final Class<T> configClass; + private final ResourceLocation id; private final boolean supportsAutoGen; private final ConfigSerializer<T> serializer; private final ConfigField<?>[] fields; private final T instance, defaults; - public ConfigClassHandlerImpl(Class<T> configClass, Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory, boolean autoGen) { + public ConfigClassHandlerImpl(Class<T> configClass, ResourceLocation id, Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory, boolean autoGen) { this.configClass = configClass; + this.id = id; this.supportsAutoGen = YACLPlatform.getEnvironment().isClient() && autoGen; try { @@ -30,8 +39,9 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { } this.fields = Arrays.stream(configClass.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(ConfigEntry.class)) - .map(field -> new ConfigFieldImpl<>(this.supportsAutoGen(), field.getAnnotation(ConfigEntry.class), new ReflectionFieldAccess<>(field, instance))) + .peek(field -> field.setAccessible(true)) + .filter(field -> field.isAnnotationPresent(SerialEntry.class) || field.isAnnotationPresent(AutoGen.class)) + .map(field -> new ConfigFieldImpl<>(new ReflectionFieldAccess<>(field, instance), new ReflectionFieldAccess<>(field, defaults), this, field.getAnnotation(SerialEntry.class), field.getAnnotation(AutoGen.class))) .toArray(ConfigField[]::new); this.serializer = serializerFactory.apply(this); } @@ -57,6 +67,11 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { } @Override + public ResourceLocation id() { + return this.id; + } + + @Override public boolean supportsAutoGen() { return this.supportsAutoGen; } @@ -65,7 +80,43 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { public YetAnotherConfigLib generateGui() { Validate.isTrue(supportsAutoGen(), "Auto GUI generation is not supported for this config class. You either need to enable it in the builder or you are attempting to create a GUI in a dedicated server environment."); - throw new IllegalStateException(); + OptionStorageImpl storage = new OptionStorageImpl(); + Map<String, CategoryAndGroups> categories = new LinkedHashMap<>(); + for (ConfigField<?> configField : fields()) { + configField.autoGen().ifPresent(autoGen -> { + CategoryAndGroups groups = categories.computeIfAbsent( + autoGen.category(), + k -> new CategoryAndGroups( + ConfigCategory.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s".formatted(id().toString(), k))), + new LinkedHashMap<>() + ) + ); + OptionAddable group = groups.groups().computeIfAbsent(autoGen.group().orElse(""), k -> { + if (k.isEmpty()) + return groups.category(); + return OptionGroup.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s.group.%s".formatted(id().toString(), autoGen.category(), k))); + }); + + Option<?> option = createOption(configField, storage); + storage.putOption(configField.access().name(), option); + group.option(option); + }); + } + categories.values().forEach(CategoryAndGroups::finaliseGroups); + + YetAnotherConfigLib.Builder yaclBuilder = YetAnotherConfigLib.createBuilder() + .save(this.serializer()::serialize) + .title(Component.translatable("yacl3.config.%s.title".formatted(this.id().toString()))); + categories.values().forEach(category -> yaclBuilder.category(category.category().build())); + + return yaclBuilder.build(); + } + + private <U> Option<U> createOption(ConfigField<U> configField, OptionStorage storage) { + return OptionFactoryRegistry.createOption(((ReflectionFieldAccess<?>) configField.access()).field(), configField, storage) + .orElseThrow(() -> new IllegalStateException("Failed to create option for field %s".formatted(configField.access().name()))); } @Override @@ -75,6 +126,7 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { public static class BuilderImpl<T> implements Builder<T> { private final Class<T> configClass; + private ResourceLocation id; private Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory; private boolean autoGen; @@ -83,6 +135,12 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { } @Override + public Builder<T> id(ResourceLocation id) { + this.id = id; + return this; + } + + @Override public Builder<T> serializer(Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory) { this.serializerFactory = serializerFactory; return this; @@ -90,12 +148,23 @@ public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { @Override public Builder<T> autoGen(boolean autoGen) { - throw new IllegalArgumentException(); + this.autoGen = autoGen; + return this; } @Override public ConfigClassHandler<T> build() { - return new ConfigClassHandlerImpl<>(configClass, serializerFactory, autoGen); + return new ConfigClassHandlerImpl<>(configClass, id, serializerFactory, autoGen); + } + } + + private record CategoryAndGroups(ConfigCategory.Builder category, Map<String, OptionAddable> groups) { + private void finaliseGroups() { + groups.forEach((name, group) -> { + if (group instanceof OptionGroup.Builder groupBuilder) { + category.group(groupBuilder.build()); + } + }); } } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java index 68bf4b8..3d79e7e 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java @@ -1,74 +1,69 @@ package dev.isxander.yacl3.config.v2.impl; -import dev.isxander.yacl3.config.v2.api.ConfigEntry; -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.api.FieldAccess; -import dev.isxander.yacl3.config.v2.api.OptionFactory; -import org.apache.commons.lang3.NotImplementedException; +import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Constructor; import java.util.Optional; public class ConfigFieldImpl<T> implements ConfigField<T> { - private final @Nullable OptionFactory<T> factory; - private final String serialName; - private final Optional<String> comment; private final FieldAccess<T> field; - private final boolean autoGen; + private final ReadOnlyFieldAccess<T> defaultField; + private final ConfigClassHandler<?> parent; + private final Optional<SerialField> serial; + private final Optional<AutoGenField<T>> autoGen; - public ConfigFieldImpl(boolean auto, ConfigEntry entry, FieldAccess<T> field) { - this.serialName = "".equals(entry.serialName()) ? field.name() : entry.serialName(); - this.comment = "".equals(entry.comment()) ? Optional.empty() : Optional.of(entry.comment()); - this.factory = auto ? makeFactory(entry.factory(), this.serialName) : null; - this.autoGen = auto; + public ConfigFieldImpl(FieldAccess<T> field, ReadOnlyFieldAccess<T> defaultField, ConfigClassHandler<?> parent, @Nullable SerialEntry config, @Nullable AutoGen autoGen) { this.field = field; + this.defaultField = defaultField; + this.parent = parent; + + this.serial = config != null + ? Optional.of( + new SerialFieldImpl( + "".equals(config.value()) ? field.name() : config.value(), + "".equals(config.comment()) ? Optional.empty() : Optional.of(config.comment()) + ) + ) + : Optional.empty(); + this.autoGen = autoGen != null + ? Optional.of( + new AutoGenFieldImpl<>( + autoGen.category(), + "".equals(autoGen.group()) ? Optional.empty() : Optional.of(autoGen.group()) + ) + ) + : Optional.empty(); } @Override - public String serialName() { - return this.serialName; + public FieldAccess<T> access() { + return field; } @Override - public Optional<String> comment() { - return this.comment; + public ReadOnlyFieldAccess<T> defaultAccess() { + return defaultField; } @Override - public FieldAccess<T> access() { - return field; + public ConfigClassHandler<?> parent() { + return parent; } @Override - public @Nullable OptionFactory<T> factory() { - return factory; + public Optional<SerialField> serial() { + return this.serial; } @Override - public boolean supportsFactory() { + public Optional<AutoGenField<T>> autoGen() { return this.autoGen; } - private OptionFactory<T> makeFactory(Class<? extends OptionFactory<?>> clazz, String name) { - if (clazz.equals(DefaultOptionFactory.class)) { - throw new NotImplementedException("Field '%s' does not have an option factory, but auto-gen is enabled.".formatted(this.serialName())); - } - - Constructor<?> constructor; - - try { - constructor = clazz.getConstructor(String.class); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Failed to find (String) constructor for option factory %s.".formatted(clazz.getName()), e); - } - - try { - return (OptionFactory<T>) constructor.newInstance(name); - } catch (ClassCastException e) { - throw new IllegalStateException("Failed to cast option factory %s to OptionFactory<%s>.".formatted(clazz.getName(), field.type().getTypeName()), e); - } catch (ReflectiveOperationException e) { - throw new IllegalStateException("Failed to create new option factory (class is '%s')".formatted(clazz.getName()), e); - } + private record SerialFieldImpl(String serialName, Optional<String> comment) implements SerialField { + } + private record AutoGenFieldImpl<T>(String category, Optional<String> group) implements AutoGenField<T> { } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java deleted file mode 100644 index e32de00..0000000 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/DefaultOptionFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.isxander.yacl3.config.v2.impl; - -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.api.OptionFactory; -import org.apache.commons.lang3.NotImplementedException; - -public class DefaultOptionFactory implements OptionFactory<Object> { - @Override - public Option<Object> create(ConfigField<Object> field) { - throw new NotImplementedException(); - } - - @Override - public Class<Object> type() { - throw new NotImplementedException(); - } -} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java new file mode 100644 index 0000000..f2f36e7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java @@ -0,0 +1,22 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.Binding; +import dev.isxander.yacl3.config.v2.api.FieldAccess; +import dev.isxander.yacl3.config.v2.api.ReadOnlyFieldAccess; + +public record FieldBackedBinding<T>(FieldAccess<T> field, ReadOnlyFieldAccess<T> defaultField) implements Binding<T> { + @Override + public T getValue() { + return field.get(); + } + + @Override + public void setValue(T value) { + field.set(value); + } + + @Override + public T defaultValue() { + return defaultField.get(); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java new file mode 100644 index 0000000..3c75a11 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/OptionStorageImpl.java @@ -0,0 +1,37 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class OptionStorageImpl implements OptionStorage { + private final Map<String, Option<?>> storage = new HashMap<>(); + private final Map<String, Consumer<Option<?>>> scheduledOperations = new HashMap<>(); + + @Override + public @Nullable Option<?> getOption(String fieldName) { + return storage.get(fieldName); + } + + @Override + public void scheduleOptionOperation(String fieldName, Consumer<Option<?>> optionConsumer) { + if (storage.containsKey(fieldName)) { + optionConsumer.accept(storage.get(fieldName)); + } else { + scheduledOperations.merge(fieldName, optionConsumer, Consumer::andThen); + } + } + + public void putOption(String fieldName, Option<?> option) { + storage.put(fieldName, option); + + Consumer<Option<?>> consumer = scheduledOperations.remove(fieldName); + if (consumer != null) { + consumer.accept(option); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java new file mode 100644 index 0000000..0a24cf5 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.network.chat.Component; + +public class BooleanImpl extends SimpleOptionFactory<Boolean, java.lang.Boolean> { + @Override + protected ControllerBuilder<java.lang.Boolean> createController(Boolean annotation, ConfigField<java.lang.Boolean> field, OptionStorage storage, Option<java.lang.Boolean> option) { + var builder = BooleanControllerBuilder.create(option) + .coloured(annotation.colored()); + switch (annotation.formatter()) { + case ON_OFF -> builder.onOffFormatter(); + case YES_NO -> builder.yesNoFormatter(); + case TRUE_FALSE -> builder.trueFalseFormatter(); + case CUSTOM -> builder.valueFormatter(v -> Component.translatable(getTranslationKey(field, java.lang.Boolean.toString(v)))); + } + return builder; + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java new file mode 100644 index 0000000..87db158 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java @@ -0,0 +1,30 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.DoubleSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class DoubleSliderImpl extends SimpleOptionFactory<DoubleSlider, Double> { + @Override + protected ControllerBuilder<Double> createController(DoubleSlider annotation, ConfigField<Double> field, OptionStorage storage, Option<Double> option) { + return DoubleSliderControllerBuilder.create(option) + .valueFormatter(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java new file mode 100644 index 0000000..b856a7a --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java @@ -0,0 +1,30 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.FloatSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class FloatSliderImpl extends SimpleOptionFactory<FloatSlider, Float> { + @Override + protected ControllerBuilder<Float> createController(FloatSlider annotation, ConfigField<Float> field, OptionStorage storage, Option<Float> option) { + return FloatSliderControllerBuilder.create(option) + .valueFormatter(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java new file mode 100644 index 0000000..c03d370 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java @@ -0,0 +1,26 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.IntSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class IntSliderImpl extends SimpleOptionFactory<IntSlider, Integer> { + @Override + protected ControllerBuilder<Integer> createController(IntSlider annotation, ConfigField<Integer> field, OptionStorage storage, Option<Integer> option) { + return IntegerSliderControllerBuilder.create(option) + .valueFormatter(v -> { + String key = getTranslationKey(field, "fmt." + v); + if (Language.getInstance().has(key)) + return Component.translatable(key); + return Component.literal(Integer.toString(v)); + }) + .range(annotation.min(), annotation.max()) + .step(annotation.step()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java new file mode 100644 index 0000000..c36c8b7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LabelImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.LabelOption; +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Label; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import net.minecraft.network.chat.Component; + +public class LabelImpl implements OptionFactory<Label, Component> { + @Override + public Option<Component> createOption(Label annotation, ConfigField<Component> field, OptionStorage storage) { + return LabelOption.create(field.access().get()); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java new file mode 100644 index 0000000..65433a3 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java @@ -0,0 +1,27 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.MasterTickBox; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; + +public class MasterTickBoxImpl extends SimpleOptionFactory<MasterTickBox, Boolean> { + @Override + protected ControllerBuilder<Boolean> createController(MasterTickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> option) { + return TickBoxControllerBuilder.create(option); + } + + @Override + protected void postInit(MasterTickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> option) { + option.addListener((opt, val) -> { + for (String child : annotation.value()) { + storage.scheduleOptionOperation(child, childOpt -> { + childOpt.setAvailable(annotation.invert() != val); + }); + } + }); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java new file mode 100644 index 0000000..1762f2d --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/OptionFactoryRegistry.java @@ -0,0 +1,54 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.OptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.*; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.config.v2.impl.autogen.*; +import dev.isxander.yacl3.impl.utils.YACLConstants; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class OptionFactoryRegistry { + private static final Map<Class<?>, OptionFactory<?, ?>> factoryMap = new HashMap<>(); + + static { + registerOptionFactory(TickBox.class, new TickBoxImpl()); + registerOptionFactory(Boolean.class, new BooleanImpl()); + registerOptionFactory(IntSlider.class, new IntSliderImpl()); + registerOptionFactory(FloatSlider.class, new FloatSliderImpl()); + registerOptionFactory(DoubleSlider.class, new DoubleSliderImpl()); + registerOptionFactory(Label.class, new LabelImpl()); + + registerOptionFactory(MasterTickBox.class, new MasterTickBoxImpl()); + } + + public static <A extends Annotation, T> void registerOptionFactory(Class<A> annotation, OptionFactory<A, T> factory) { + factoryMap.put(annotation, factory); + } + + public static <T> Optional<Option<T>> createOption(Field field, ConfigField<T> configField, OptionStorage storage) { + Annotation[] annotations = Arrays.stream(field.getAnnotations()) + .filter(annotation -> factoryMap.containsKey(annotation.annotationType())) + .toArray(Annotation[]::new); + + if (annotations.length != 1) { + YACLConstants.LOGGER.warn("Found {} option factory annotations on field {}, expected 1", annotations.length, field); + + if (annotations.length == 0) { + return Optional.empty(); + } + } + + Annotation annotation = annotations[0]; + // noinspection unchecked + OptionFactory<Annotation, T> factory = (OptionFactory<Annotation, T>) factoryMap.get(annotation.annotationType()); + return Optional.of(factory.createOption(annotation, configField, storage)); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java new file mode 100644 index 0000000..202534e --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/TickBoxImpl.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.OptionStorage; +import dev.isxander.yacl3.config.v2.api.autogen.TickBox; + +public class TickBoxImpl extends SimpleOptionFactory<TickBox, Boolean> { + @Override + protected ControllerBuilder<Boolean> createController(TickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> option) { + return TickBoxControllerBuilder.create(option); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java index 8bbc079..712a459 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java @@ -1,10 +1,7 @@ package dev.isxander.yacl3.config.v2.impl.serializer; import com.google.gson.*; -import dev.isxander.yacl3.config.v2.api.ConfigClassHandler; -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.api.ConfigSerializer; -import dev.isxander.yacl3.config.v2.api.GsonConfigSerializerBuilder; +import dev.isxander.yacl3.config.v2.api.*; import dev.isxander.yacl3.impl.utils.YACLConstants; import dev.isxander.yacl3.platform.YACLPlatform; import net.minecraft.network.chat.Component; @@ -35,14 +32,19 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { JsonObject root = new JsonObject(); for (ConfigField<?> field : config.fields()) { - if (YACLPlatform.isDevelopmentEnv() && field.comment().isPresent()) { - YACLConstants.LOGGER.error("Config field '{}' has a comment, but comments are not supported by Gson. Please remove the comment or switch to a different serializer. This log will not be shown in production.", field.serialName()); + SerialField serial = field.serial().orElse(null); + if (serial == null) { + continue; + } + + if (YACLPlatform.isDevelopmentEnv() && serial.comment().isPresent()) { + YACLConstants.LOGGER.error("Config field '{}' has a comment, but comments are not supported by Gson. Please remove the comment or switch to a different serializer. This log will not be shown in production.", serial.serialName()); } try { - root.add(field.serialName(), gson.toJsonTree(field.access().get())); + root.add(serial.serialName(), gson.toJsonTree(field.access().get())); } catch (Exception e) { - YACLConstants.LOGGER.error("Failed to serialize config field '{}'.", field.serialName(), e); + YACLConstants.LOGGER.error("Failed to serialize config field '{}'.", serial.serialName(), e); } } @@ -75,17 +77,22 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { List<String> unconsumedKeys = new ArrayList<>(root.keySet()); for (ConfigField<?> field : config.fields()) { - if (root.containsKey(field.serialName())) { + SerialField serial = field.serial().orElse(null); + if (serial == null) { + continue; + } + + if (root.containsKey(serial.serialName())) { try { - field.access().set(gson.fromJson(root.get(field.serialName()), field.access().type())); + field.access().set(gson.fromJson(root.get(serial.serialName()), field.access().type())); } catch (Exception e) { - YACLConstants.LOGGER.error("Failed to deserialize config field '{}'.", field.serialName(), e); + YACLConstants.LOGGER.error("Failed to deserialize config field '{}'.", serial.serialName(), e); } } else { - YACLConstants.LOGGER.warn("Config field '{}' was not found in the config file. Skipping.", field.serialName()); + YACLConstants.LOGGER.warn("Config field '{}' was not found in the config file. Skipping.", serial.serialName()); } - unconsumedKeys.remove(field.serialName()); + unconsumedKeys.remove(serial.serialName()); } if (!unconsumedKeys.isEmpty()) { |