From d579c19f6b9113b847b4593afaddd23f613b09c3 Mon Sep 17 00:00:00 2001 From: JFronny Date: Fri, 26 Apr 2024 09:11:28 +0200 Subject: [PATCH] chore(config-core): plumb TypeTokens throughout internal representation --- .../libjf/config/plugin/ConfigProcessor.java | 2 +- .../libjf/config/api/v2/EntryInfo.java | 16 ++++- .../libjf/config/api/v2/type/Type.java | 37 ++++++++-- .../libjf/config/impl/dsl/DslEntryInfo.java | 52 ++++++++------ .../impl/dsl/NothingSerializedException.java | 4 ++ .../rci/entry/MirrorEntryInfoBase.java | 11 +++ .../impl/ui/tiny/TinyConfigScreenFactory.java | 71 +++++++++---------- .../ui/tiny/entry/EntryInfoWidgetBuilder.java | 9 +-- .../libjf-config-ui-tiny/lang/en_us.json | 4 +- .../libjf/config/test/tiny/TestConfig.java | 6 ++ 10 files changed, 142 insertions(+), 70 deletions(-) create mode 100644 libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/NothingSerializedException.java diff --git a/libjf-config-compiler-plugin-v2/src/main/java/io/gitlab/jfronny/libjf/config/plugin/ConfigProcessor.java b/libjf-config-compiler-plugin-v2/src/main/java/io/gitlab/jfronny/libjf/config/plugin/ConfigProcessor.java index 9bdc876..7adfc9f 100644 --- a/libjf-config-compiler-plugin-v2/src/main/java/io/gitlab/jfronny/libjf/config/plugin/ConfigProcessor.java +++ b/libjf-config-compiler-plugin-v2/src/main/java/io/gitlab/jfronny/libjf/config/plugin/ConfigProcessor.java @@ -174,7 +174,7 @@ public class ConfigProcessor extends AbstractProcessor2 { default -> { code.add("$L, $L, ", e.min(), e.max()); if (tm instanceof DeclaredType dt && !dt.getTypeArguments().isEmpty()) { - code.add("$T.ofClass(new $T<$T>() {}.getType())", Type.class, TypeToken.class, tm); + code.add("$T.ofToken(new $T<$T>() {})", Type.class, TypeToken.class, tm); } else code.add("$T.class", tm); code.add(", $L, () -> $T.$L, value -> $T.$L = value", e.width(), source, name, source, name); } diff --git a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/EntryInfo.java b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/EntryInfo.java index e60eea1..58f4847 100644 --- a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/EntryInfo.java +++ b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/EntryInfo.java @@ -1,12 +1,14 @@ package io.gitlab.jfronny.libjf.config.api.v2; +import io.gitlab.jfronny.commons.serialize.MalformedDataException; import io.gitlab.jfronny.commons.serialize.SerializeReader; import io.gitlab.jfronny.commons.serialize.SerializeWriter; +import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken; +import io.gitlab.jfronny.libjf.LibJf; import io.gitlab.jfronny.libjf.config.api.v2.type.Type; import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo; import org.jetbrains.annotations.ApiStatus; -import java.io.IOException; import java.lang.reflect.Field; public interface EntryInfo { @@ -77,6 +79,18 @@ public interface EntryInfo { */ > void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException; + default > T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException { + return LibJf.MAPPER.getAdapter(getTypeToken()).deserialize(reader); + } + + default > void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx, MalformedDataException { + LibJf.MAPPER.getAdapter(getTypeToken()).serialize(value, writer); + } + + default TypeToken getTypeToken() { + return (TypeToken) getValueType().asToken(); + } + /** * @return Get the width for this entry */ diff --git a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/type/Type.java b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/type/Type.java index 1febe17..ba508e7 100644 --- a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/type/Type.java +++ b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/api/v2/type/Type.java @@ -1,5 +1,6 @@ package io.gitlab.jfronny.libjf.config.api.v2.type; +import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken; import org.jetbrains.annotations.Nullable; /** @@ -14,7 +15,19 @@ public sealed interface Type { else if (klazz == String.class) return TString.INSTANCE; else if (klazz == boolean.class || klazz == Boolean.class) return TBool.INSTANCE; else if (klazz instanceof Class k && k.isEnum()) return new TEnum<>(k); - else return new TUnknown(klazz); + else return new TUnknown(klazz, TypeToken.get(klazz)); + } + + static Type ofToken(TypeToken token) { + java.lang.reflect.Type klazz = token.getRawType(); + if (klazz == int.class || klazz == Integer.class) return TInt.INSTANCE; + else if (klazz == long.class || klazz == Long.class) return TLong.INSTANCE; + else if (klazz == float.class || klazz == Float.class) return TFloat.INSTANCE; + else if (klazz == double.class || klazz == Double.class) return TDouble.INSTANCE; + else if (klazz == String.class) return TString.INSTANCE; + else if (klazz == boolean.class || klazz == Boolean.class) return TBool.INSTANCE; + else if (klazz instanceof Class k && k.isEnum()) return new TEnum<>(k); + else return new TUnknown(klazz, token); } default boolean isInt() { @@ -45,6 +58,10 @@ public sealed interface Type { } @Nullable java.lang.reflect.Type asClass(); + @Nullable default TypeToken asToken() { + java.lang.reflect.Type type = asClass(); + return type == null ? null : TypeToken.get(type); + } String getName(); @@ -162,13 +179,13 @@ public sealed interface Type { } } - record TEnum(@Nullable Class klazz, String name, T[] options) implements Type { + record TEnum(@Nullable Class klazz, TypeToken token, String name, T[] options) implements Type { public TEnum(Class klazz) { - this(klazz, klazz.getSimpleName(), klazz.getEnumConstants()); + this(klazz, TypeToken.get(klazz), klazz.getSimpleName(), klazz.getEnumConstants()); } public static TEnum create(String name, String[] options) { - return new TEnum<>(null, name, options); + return new TEnum<>(null, null, name, options); } @Override @@ -181,6 +198,11 @@ public sealed interface Type { return klazz; } + @Override + public @Nullable TypeToken asToken() { + return token; + } + @Override public String getName() { return name; @@ -194,12 +216,17 @@ public sealed interface Type { } } - record TUnknown(java.lang.reflect.Type klazz) implements Type { + record TUnknown(java.lang.reflect.Type klazz, TypeToken token) implements Type { @Override public @Nullable java.lang.reflect.Type asClass() { return klazz; } + @Override + public @Nullable TypeToken asToken() { + return token; + } + @Override public String getName() { return klazz instanceof Class k ? k.getSimpleName() : klazz.getTypeName(); diff --git a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/DslEntryInfo.java b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/DslEntryInfo.java index 7a2454d..6130c64 100644 --- a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/DslEntryInfo.java +++ b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/DslEntryInfo.java @@ -1,6 +1,5 @@ package io.gitlab.jfronny.libjf.config.impl.dsl; -import io.gitlab.jfronny.commons.Serializer; import io.gitlab.jfronny.commons.serialize.MalformedDataException; import io.gitlab.jfronny.commons.serialize.SerializeReader; import io.gitlab.jfronny.commons.serialize.SerializeWriter; @@ -63,12 +62,12 @@ public class DslEntryInfo implements EntryInfo { } catch (IllegalAccessException ignored) { } //noinspection unchecked,rawtypes - return new DslEntryInfo( + return new DslEntryInfo<>( field.getName(), defaultValue, () -> field.get(null), v -> field.set(null, v), - (Type) Type.ofClass(field.getGenericType()), + Type.ofClass(field.getGenericType()), entry == null ? 100 : entry.width(), entry == null ? Double.NEGATIVE_INFINITY : entry.min(), entry == null ? Double.POSITIVE_INFINITY : entry.max() @@ -122,7 +121,7 @@ public class DslEntryInfo implements EntryInfo { } if (valueOriginal != value) { try { - setUnchecked(value); + setValue(cast(value)); } catch (IllegalAccessException e) { LibJf.LOGGER.error("Could not write value", e); } @@ -131,53 +130,66 @@ public class DslEntryInfo implements EntryInfo { @Override public > void loadFromJson(Reader reader) throws TEx, IllegalAccessException { + try { + setValue(deserializeOneFrom(reader)); + } catch (MalformedDataException e) { + LibJf.LOGGER.error("Could not read " + name, e); + } catch (NothingSerializedException ignored) { + } + } + + @Override + public > T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException { var next = reader.peek(); if (type.isBool()) { - if (next == Token.BOOLEAN) setUnchecked(reader.nextBoolean()); + if (next == Token.BOOLEAN) return cast(reader.nextBoolean()); else LibJf.LOGGER.error("Unexpected value for " + name + ": expected boolean but got " + next); } else if (type.isString()) { - if (next == Token.STRING || next == Token.NUMBER) setUnchecked(reader.nextString()); - else if (next == Token.BOOLEAN) setUnchecked(Boolean.toString(reader.nextBoolean())); + if (next == Token.STRING || next == Token.NUMBER) return cast(reader.nextString()); + else if (next == Token.BOOLEAN) return cast(Boolean.toString(reader.nextBoolean())); else if (next == Token.NULL) { reader.nextNull(); - setUnchecked(null); + return cast(null); } else LibJf.LOGGER.error("Unexpected value for " + name + ": expected string but got " + next); } else if (type.isInt()) { - if (next == Token.NUMBER) setUnchecked(reader.nextInt()); + if (next == Token.NUMBER) return cast(reader.nextInt()); else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next); } else if (type.isLong()) { - if (next == Token.NUMBER) setUnchecked(reader.nextLong()); + if (next == Token.NUMBER) return cast(reader.nextLong()); else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next); } else if (type.isDouble()) { if (next == Token.NUMBER) { - setUnchecked(reader.nextDouble()); + return cast(reader.nextDouble()); } else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next); } else if (type.isFloat()) { - if (next == Token.NUMBER) setUnchecked((float) reader.nextDouble()); + if (next == Token.NUMBER) return cast((float) reader.nextDouble()); else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next); } else if (type.isEnum()) { Type.TEnum e = (Type.TEnum) type; - if (next == Token.STRING) setUnchecked(e.optionForString(reader.nextString())); + if (next == Token.STRING) return cast(e.optionForString(reader.nextString())); else LibJf.LOGGER.error("Unexpected value for " + name + ": expected string but got " + next); } else { try { - setValue((T) LibJf.MAPPER.getAdapter(TypeToken.get(type.asClass())).deserialize(reader)); + return cast((T) LibJf.MAPPER.getAdapter(type.asToken()).deserialize(reader)); } catch (MalformedDataException e) { LibJf.LOGGER.error("Could not read " + name, e); } } + throw new NothingSerializedException(); } - @SuppressWarnings("unchecked") - private void setUnchecked(Object object) throws IllegalAccessException { - if (object == null) return; - setValue((T) object); + private T cast(Object object) { + return (T) object; } @Override public > void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException { - T value = getValue(); + serializeOneTo(getValue(), translationPrefix, writer); + } + + @Override + public > void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx { String commentText; if ((commentText = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip")) != null) { writer.comment(commentText); @@ -187,7 +199,7 @@ public class DslEntryInfo implements EntryInfo { } writer.name(name); try { - LibJf.MAPPER.getAdapter((TypeToken) TypeToken.get(Objects.requireNonNullElse(type.asClass(), String.class))).serialize(value, writer); + LibJf.MAPPER.getAdapter((TypeToken) Objects.requireNonNullElse(type.asToken(), TypeToken.get(String.class))).serialize(value, writer); } catch (MalformedDataException e) { LibJf.LOGGER.error("Could not write " + name, e); } diff --git a/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/NothingSerializedException.java b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/NothingSerializedException.java new file mode 100644 index 0000000..804df8c --- /dev/null +++ b/libjf-config-core-v2/src/main/java/io/gitlab/jfronny/libjf/config/impl/dsl/NothingSerializedException.java @@ -0,0 +1,4 @@ +package io.gitlab.jfronny.libjf.config.impl.dsl; + +public class NothingSerializedException extends RuntimeException { +} diff --git a/libjf-config-network-v0/src/main/java/io/gitlab/jfronny/libjf/config/impl/network/rci/entry/MirrorEntryInfoBase.java b/libjf-config-network-v0/src/main/java/io/gitlab/jfronny/libjf/config/impl/network/rci/entry/MirrorEntryInfoBase.java index 84f70d4..055eb40 100644 --- a/libjf-config-network-v0/src/main/java/io/gitlab/jfronny/libjf/config/impl/network/rci/entry/MirrorEntryInfoBase.java +++ b/libjf-config-network-v0/src/main/java/io/gitlab/jfronny/libjf/config/impl/network/rci/entry/MirrorEntryInfoBase.java @@ -1,5 +1,6 @@ package io.gitlab.jfronny.libjf.config.impl.network.rci.entry; +import io.gitlab.jfronny.commons.serialize.MalformedDataException; import io.gitlab.jfronny.commons.serialize.SerializeReader; import io.gitlab.jfronny.commons.serialize.SerializeWriter; import io.gitlab.jfronny.libjf.config.api.v2.EntryInfo; @@ -39,6 +40,16 @@ public abstract class MirrorEntryInfoBase extends MirrorObject implements Ent throw new UnsupportedOperationException(); } + @Override + public > T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException { + throw new UnsupportedOperationException(); + } + + @Override + public > void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx, MalformedDataException { + throw new UnsupportedOperationException(); + } + @Override public void fix() { PacketByteBuf buf = PacketByteBufs.create(); diff --git a/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/TinyConfigScreenFactory.java b/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/TinyConfigScreenFactory.java index 49686d5..e43822d 100644 --- a/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/TinyConfigScreenFactory.java +++ b/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/TinyConfigScreenFactory.java @@ -1,8 +1,6 @@ package io.gitlab.jfronny.libjf.config.impl.ui.tiny; -import io.gitlab.jfronny.commons.Serializer; import io.gitlab.jfronny.commons.serialize.Transport; -import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken; import io.gitlab.jfronny.commons.serialize.json.JsonReader; import io.gitlab.jfronny.libjf.LibJf; import io.gitlab.jfronny.libjf.config.api.v2.ConfigInstance; @@ -26,47 +24,48 @@ public class TinyConfigScreenFactory implements ConfigScreenFactory s.equals(CategoryBuilder.CONFIG_PRESET_DEFAULT)) && config.getReferencedConfigs().isEmpty() && config.getCategories().isEmpty()) { - EntryInfo entry = config.getEntries().getFirst(); + EntryInfo entry = config.getEntries().getFirst(); Type type = entry.supportsRepresentation() ? entry.getValueType() : null; if (type != null && !type.isInt() && !type.isLong() && !type.isFloat() && !type.isDouble() && !type.isString() && !type.isBool() && !type.isEnum()) { - final String jsonified; - try { - var value = entry.getValue(); - jsonified = LibJf.LENIENT_TRANSPORT.write(writer -> LibJf.MAPPER.serialize(value, writer)); - } catch (IllegalAccessException | IOException e) { - throw new RuntimeException(e); - } - String key = config.getTranslationPrefix() + entry.getName(); - return new Built(new EditorScreen( - Text.translatable(key), - I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null, - parent, - jsonified, - json -> { - try { - entry.setValue(LibJf.LENIENT_TRANSPORT.read( - json, - (Transport.Returnable) reader -> LibJf.MAPPER - .getAdapter(TypeToken.get(type.asClass())) - .deserialize(reader))); - entry.setValue(Serializer.getInstance().deserialize(json, type.asClass())); - config.write(); - } catch (Throwable e) { - LibJf.LOGGER.error("Could not write element", e); - SystemToast.add( - MinecraftClient.getInstance().getToastManager(), - SystemToast.Type.PACK_LOAD_FAILURE, - Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.title"), - Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.description") - ); - } - } - )); + return createJson(config, parent, entry); } } return new Built(new TinyConfigScreen(config, parent)); } + private Built createJson(ConfigInstance config, Screen parent, EntryInfo entry) { + final String jsonified; + try { + var value = entry.getValue(); + jsonified = LibJf.LENIENT_TRANSPORT.write(writer -> entry.serializeOneTo(value, config.getTranslationPrefix(), writer)); + } catch (IllegalAccessException | IOException e) { + throw new RuntimeException(e); + } + String key = config.getTranslationPrefix() + entry.getName(); + return new Built(new EditorScreen( + Text.translatable(key), + I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null, + parent, + jsonified, + json -> { + try { + entry.setValue(LibJf.LENIENT_TRANSPORT.read( + json, + (Transport.Returnable) entry::deserializeOneFrom)); + config.write(); + } catch (Throwable e) { + LibJf.LOGGER.error("Could not write element", e); + SystemToast.add( + MinecraftClient.getInstance().getToastManager(), + SystemToast.Type.PACK_LOAD_FAILURE, + Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.title"), + Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.description") + ); + } + } + )); + } + @Override public int getPriority() { return 0; diff --git a/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java b/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java index 4b8713a..28ecc85 100644 --- a/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java +++ b/libjf-config-ui-tiny/src/client/java/io/gitlab/jfronny/libjf/config/impl/ui/tiny/entry/EntryInfoWidgetBuilder.java @@ -1,9 +1,7 @@ package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry; -import io.gitlab.jfronny.commons.Serializer; import io.gitlab.jfronny.commons.ref.R; import io.gitlab.jfronny.commons.serialize.Transport; -import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken; import io.gitlab.jfronny.commons.serialize.json.JsonReader; import io.gitlab.jfronny.commons.throwable.Try; import io.gitlab.jfronny.libjf.LibJf; @@ -175,7 +173,7 @@ public class EntryInfoWidgetBuilder { final String jsonified; if (state.tempValue == null) { try { - jsonified = LibJf.LENIENT_TRANSPORT.write(writer -> LibJf.MAPPER.serialize(state.cachedValue, writer)); + jsonified = LibJf.LENIENT_TRANSPORT.write(writer -> info.serializeOneTo(state.cachedValue, config.getTranslationPrefix(), writer)); } catch (Throwable e) { LibJf.LOGGER.error("Could not stringify element", e); SystemToast.add( @@ -199,9 +197,8 @@ public class EntryInfoWidgetBuilder { try { state.updateCache(LibJf.LENIENT_TRANSPORT.read( json, - (Transport.Returnable) reader -> LibJf.MAPPER - .getAdapter((TypeToken) TypeToken.get(info.getValueType().asClass())) - .deserialize(reader))); + (Transport.Returnable) info::deserializeOneFrom + )); state.tempValue = null; } catch (Throwable e) { LibJf.LOGGER.error("Could not write element", e); diff --git a/libjf-config-ui-tiny/src/client/resources/assets/libjf-config-ui-tiny/lang/en_us.json b/libjf-config-ui-tiny/src/client/resources/assets/libjf-config-ui-tiny/lang/en_us.json index 1bd4da7..1bd0d6e 100644 --- a/libjf-config-ui-tiny/src/client/resources/assets/libjf-config-ui-tiny/lang/en_us.json +++ b/libjf-config-ui-tiny/src/client/resources/assets/libjf-config-ui-tiny/lang/en_us.json @@ -1,4 +1,6 @@ { "libjf-config-ui-tiny.entry.json.read.fail.title": "Could not read", - "libjf-config-ui-tiny.entry.json.read.fail.description": "The given entry could not be stringified. Please edit the config manually" + "libjf-config-ui-tiny.entry.json.read.fail.description": "The given entry could not be read from a string. Please edit the config manually", + "libjf-config-ui-tiny.entry.json.write.fail.title": "Could not write", + "libjf-config-ui-tiny.entry.json.write.fail.description": "The given entry could not be stringified. Please edit the config manually" } \ No newline at end of file diff --git a/libjf-config-ui-tiny/src/testmod/java/io/gitlab/jfronny/libjf/config/test/tiny/TestConfig.java b/libjf-config-ui-tiny/src/testmod/java/io/gitlab/jfronny/libjf/config/test/tiny/TestConfig.java index 4b3802d..dc48443 100644 --- a/libjf-config-ui-tiny/src/testmod/java/io/gitlab/jfronny/libjf/config/test/tiny/TestConfig.java +++ b/libjf-config-ui-tiny/src/testmod/java/io/gitlab/jfronny/libjf/config/test/tiny/TestConfig.java @@ -1,7 +1,10 @@ package io.gitlab.jfronny.libjf.config.test.tiny; +import io.gitlab.jfronny.commons.data.String2ObjectMap; +import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken; import io.gitlab.jfronny.libjf.config.api.v2.JfCustomConfig; import io.gitlab.jfronny.libjf.config.api.v2.dsl.DSL; +import io.gitlab.jfronny.libjf.config.api.v2.type.Type; public class TestConfig implements JfCustomConfig { private int value1 = 0; @@ -12,6 +15,7 @@ public class TestConfig implements JfCustomConfig { private int value4 = 0; private String value5 = ""; private boolean value6 = false; + private String2ObjectMap map = new String2ObjectMap<>(); @Override public void register(DSL.Defaulted dsl) { @@ -30,6 +34,8 @@ public class TestConfig implements JfCustomConfig { .value("value5", value5, () -> value5, v -> value5 = v) ).category("ca6", builder1 -> builder1 .value("value6", value6, () -> value6, v -> value6 = v) + ).category("ca7", builder1 -> builder1 + .value("mappy", map, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Type.ofToken(new TypeToken>() {}), 100, () -> map, v -> map = v) ) ); }