chore(config-core): plumb TypeTokens throughout internal representation
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2024-04-26 09:11:28 +02:00
parent 64f0f38668
commit d579c19f6b
Signed by: Johannes
GPG Key ID: E76429612C2929F4
10 changed files with 142 additions and 70 deletions

View File

@ -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);
}

View File

@ -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<T> {
@ -77,6 +79,18 @@ public interface EntryInfo<T> {
*/
<TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException;
default <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException {
return LibJf.MAPPER.getAdapter(getTypeToken()).deserialize(reader);
}
default <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx, MalformedDataException {
LibJf.MAPPER.getAdapter(getTypeToken()).serialize(value, writer);
}
default TypeToken<T> getTypeToken() {
return (TypeToken<T>) getValueType().asToken();
}
/**
* @return Get the width for this entry
*/

View File

@ -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<T>(@Nullable Class<T> klazz, String name, T[] options) implements Type {
record TEnum<T>(@Nullable Class<T> klazz, TypeToken<T> token, String name, T[] options) implements Type {
public TEnum(Class<T> klazz) {
this(klazz, klazz.getSimpleName(), klazz.getEnumConstants());
this(klazz, TypeToken.get(klazz), klazz.getSimpleName(), klazz.getEnumConstants());
}
public static TEnum<String> 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();

View File

@ -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<T> implements EntryInfo<T> {
} catch (IllegalAccessException ignored) {
}
//noinspection unchecked,rawtypes
return new DslEntryInfo<Object>(
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<T> implements EntryInfo<T> {
}
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<T> implements EntryInfo<T> {
@Override
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> 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 <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> 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<T> e = (Type.TEnum<T>) 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 <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException {
T value = getValue();
serializeOneTo(getValue(), translationPrefix, writer);
}
@Override
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> 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<T> implements EntryInfo<T> {
}
writer.name(name);
try {
LibJf.MAPPER.getAdapter((TypeToken<T>) TypeToken.get(Objects.requireNonNullElse(type.asClass(), String.class))).serialize(value, writer);
LibJf.MAPPER.getAdapter((TypeToken<T>) Objects.requireNonNullElse(type.asToken(), TypeToken.get(String.class))).serialize(value, writer);
} catch (MalformedDataException e) {
LibJf.LOGGER.error("Could not write " + name, e);
}

View File

@ -0,0 +1,4 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
public class NothingSerializedException extends RuntimeException {
}

View File

@ -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<T> extends MirrorObject implements Ent
throw new UnsupportedOperationException();
}
@Override
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException {
throw new UnsupportedOperationException();
}
@Override
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx, MalformedDataException {
throw new UnsupportedOperationException();
}
@Override
public void fix() {
PacketByteBuf buf = PacketByteBufs.create();

View File

@ -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<Screen, Tiny
&& config.getPresets().keySet().stream().allMatch(s -> 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<JsonReader, ? extends Object, IOException>) 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 <T> Built createJson(ConfigInstance config, Screen parent, EntryInfo<T> 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<JsonReader, ? extends T, IOException>) 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;

View File

@ -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<JsonReader, ? extends T, IOException>) reader -> LibJf.MAPPER
.getAdapter((TypeToken<T>) TypeToken.get(info.getValueType().asClass()))
.deserialize(reader)));
(Transport.Returnable<JsonReader, ? extends T, IOException>) info::deserializeOneFrom
));
state.tempValue = null;
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);

View File

@ -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"
}

View File

@ -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<String> 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<String2ObjectMap<String>>() {}), 100, () -> map, v -> map = v)
)
);
}