refactor!(core, serde-extension): Extend entry serde to make use of SubEntryKey for structured entries

This commit is contained in:
2026-04-20 13:46:39 +02:00
parent cd7ac288d4
commit 3c39848b5d
8 changed files with 118 additions and 43 deletions

View File

@@ -21,6 +21,7 @@ import de.siphalor.tweed5.coat.bridge.api.mapping.handler.BasicTweedCoatEntryHan
import de.siphalor.tweed5.coat.bridge.api.mapping.handler.ConvertingTweedCoatEntryHandler;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
@@ -328,7 +329,8 @@ public class TweedCoatMappersImpl {
@Override
public <T, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry(
TweedDataReader reader,
C entry
C entry,
SubEntryKey key
) {
return TweedReadResult.empty();
}

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.core.api.entry;
import de.siphalor.tweed5.core.api.Arity;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
@@ -9,6 +10,8 @@ import java.util.Map;
import java.util.function.Consumer;
public interface CollectionConfigEntry<E, T extends Collection<E>> extends StructuredConfigEntry<T> {
String ELEMENT_KEY = "element";
@Override
default CollectionConfigEntry<E, T> apply(Consumer<ConfigEntry<T>> function) {
StructuredConfigEntry.super.apply(function);
@@ -37,7 +40,7 @@ public interface CollectionConfigEntry<E, T extends Collection<E>> extends Struc
if (visitor.enterStructuredEntry(this, value)) {
int index = 0;
for (E item : value) {
SubEntryKey subEntryKey = SubEntryKey.structured("element", Integer.toString(index));
SubEntryKey subEntryKey = SubEntryKey.structured(ELEMENT_KEY, Integer.toString(index));
if (visitor.enterSubEntry(subEntryKey)) {
elementEntry().visitInOrder(visitor, item);
visitor.leaveSubEntry(subEntryKey);
@@ -50,10 +53,10 @@ public interface CollectionConfigEntry<E, T extends Collection<E>> extends Struc
@Override
default Map<String, ConfigEntry<?>> subEntries() {
return Collections.singletonMap("element", elementEntry());
return Collections.singletonMap(ELEMENT_KEY, elementEntry());
}
ConfigEntry<E> elementEntry();
T instantiateCollection(int size);
@NonNull T instantiateCollection(int size);
}

View File

@@ -7,6 +7,7 @@ import java.util.function.Consumer;
public interface NullableConfigEntry<T extends @Nullable Object> extends AddressableStructuredConfigEntry<T> {
String NON_NULL_KEY = ":nonNull";
SubEntryKey NON_NULL_SUB_ENTRY_KEY = SubEntryKey.transparentAddressable(NON_NULL_KEY, NON_NULL_KEY);
@Override
default NullableConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.serde.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
@@ -11,6 +12,6 @@ public interface TweedReadContext {
Patchwork extensionsData();
<T extends @Nullable Object, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry(
TweedDataReader reader, C entry
TweedDataReader reader, C entry, SubEntryKey key
);
}

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.serde.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
@@ -10,6 +11,6 @@ public interface TweedWriteContext {
Patchwork extensionsData();
<T extends @Nullable Object, C extends ConfigEntry<T>> void writeSubEntry(
TweedDataVisitor writer, @Nullable T value, C entry
TweedDataVisitor writer, C entry, SubEntryKey key, @Nullable T value
) throws TweedEntryWriteException, TweedDataWriteException;
}

View File

@@ -1,9 +1,6 @@
package de.siphalor.tweed5.serde.extension.impl;
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
import de.siphalor.tweed5.core.api.entry.*;
import de.siphalor.tweed5.serde.extension.api.*;
import de.siphalor.tweed5.serde.extension.api.read.result.ThrowingReadFunction;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
@@ -13,6 +10,7 @@ import de.siphalor.tweed5.serde_api.api.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
@@ -50,7 +48,7 @@ public class TweedEntryReaderWriterImpls {
return TweedReadResult.failed(TweedReadIssue.error(e, context));
}
return context.readSubEntry(reader, entry.nonNullEntry());
return context.readSubEntry(reader, entry.nonNullEntry(), NullableConfigEntry.NON_NULL_SUB_ENTRY_KEY);
}
@Override
@@ -63,7 +61,7 @@ public class TweedEntryReaderWriterImpls {
if (value == null) {
writer.visitNull();
} else {
context.writeSubEntry(writer, value, entry.nonNullEntry());
context.writeSubEntry(writer, entry.nonNullEntry(), NullableConfigEntry.NON_NULL_SUB_ENTRY_KEY, value);
}
}
}
@@ -124,7 +122,7 @@ public class TweedEntryReaderWriterImpls {
}
@RequiredArgsConstructor
public static class EnumReaderWriter<T extends Enum<?>> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
public static class EnumReaderWriter<T extends @Nullable Enum<?>> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
@Override
public TweedReadResult<T> read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) {
//noinspection unchecked,rawtypes
@@ -140,7 +138,7 @@ public class TweedEntryReaderWriterImpls {
@Override
public void write(
TweedDataVisitor writer,
@Nullable T value,
T value,
ConfigEntry<T> entry,
TweedWriteContext context
) throws TweedEntryWriteException, TweedDataWriteException {
@@ -149,7 +147,7 @@ public class TweedEntryReaderWriterImpls {
}
}
public static class CollectionReaderWriter<T extends @Nullable Object, C extends Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> {
public static class CollectionReaderWriter<T extends @Nullable Object, C extends @Nullable Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> {
@Override
public TweedReadResult<C> read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) {
return requireToken(reader, TweedDataToken::isListStart, "Expected list start", context).andThen(_token -> {
@@ -160,6 +158,7 @@ public class TweedEntryReaderWriterImpls {
ConfigEntry<T> elementEntry = entry.elementEntry();
List<T> list = new ArrayList<>(20);
List<TweedReadIssue> issues = new ArrayList<>();
int i = 0;
while (true) {
try {
TweedDataToken token = reader.peekToken();
@@ -167,7 +166,11 @@ public class TweedEntryReaderWriterImpls {
reader.readToken();
break;
} else if (token.isListValue()) {
TweedReadResult<T> elementResult = context.readSubEntry(reader, elementEntry);
TweedReadResult<T> elementResult = context.readSubEntry(
reader,
elementEntry,
SubEntryKey.structured(CollectionConfigEntry.ELEMENT_KEY, Integer.toString(i))
);
issues.addAll(Arrays.asList(elementResult.issues()));
if (elementResult.isFailed() || elementResult.isError()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
@@ -184,6 +187,7 @@ public class TweedEntryReaderWriterImpls {
} catch (TweedDataReadException e) {
issues.add(TweedReadIssue.error(e, context));
}
i++;
}
C result = entry.instantiateCollection(list.size());
result.addAll(list);
@@ -201,12 +205,16 @@ public class TweedEntryReaderWriterImpls {
return;
}
ConfigEntry<T> elementEntry = entry.elementEntry();
writer.visitListStart();
for (T element : value) {
context.writeSubEntry(writer, element, elementEntry);
visitStructuredEntryShallow(entry, value, new ShallowValueVisitor() {
@Override
public <U extends @Nullable Object> void visit(ConfigEntry<U> entry, SubEntryKey key, U value)
throws TweedDataWriteException, TweedEntryWriteException {
context.writeSubEntry(writer, entry, key, value);
}
});
writer.visitListEnd();
}
}
@@ -236,7 +244,13 @@ public class TweedEntryReaderWriterImpls {
}
continue;
}
TweedReadResult<Object> subEntryResult = context.readSubEntry(reader, subEntry);
TweedReadResult<Object> subEntryResult = context.readSubEntry(
reader,
subEntry,
SubEntryKey.addressable(key, key, key)
);
issues.addAll(Arrays.asList(subEntryResult.issues()));
if (subEntryResult.isFailed() || subEntryResult.isError()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
@@ -268,15 +282,16 @@ public class TweedEntryReaderWriterImpls {
writer.visitMapStart();
//noinspection unchecked
Map<String, ConfigEntry<Object>> compoundEntries = (Map<String, ConfigEntry<Object>>)(Map<?, ?>) entry.subEntries();
for (Map.Entry<String, ConfigEntry<Object>> e : compoundEntries.entrySet()) {
String key = e.getKey();
ConfigEntry<Object> subEntry = e.getValue();
writer.visitMapEntryKey(key);
context.writeSubEntry(writer, entry.get(value, key), subEntry);
visitStructuredEntryShallow(entry, value, new ShallowValueVisitor() {
@Override
public <U extends @Nullable Object> void visit(ConfigEntry<U> subEntry, SubEntryKey key, U subValue)
throws TweedDataWriteException, TweedEntryWriteException {
if (key.value() != null) {
writer.visitMapEntryKey(key.value());
context.writeSubEntry(writer, subEntry, key, subValue);
}
}
});
writer.visitMapEnd();
}
@@ -284,7 +299,11 @@ public class TweedEntryReaderWriterImpls {
public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry<Object>> {
@Override
public TweedReadResult<@Nullable Object> read(TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) {
public TweedReadResult<@Nullable Object> read(
TweedDataReader reader,
ConfigEntry<@Nullable Object> entry,
TweedReadContext context
) {
try {
TweedDataToken token = reader.readToken();
if (!token.isListStart() && !token.isMapStart()) {
@@ -317,7 +336,12 @@ public class TweedEntryReaderWriterImpls {
}
@Override
public void write(TweedDataVisitor writer, @Nullable Object value, ConfigEntry<Object> entry, TweedWriteContext context) throws TweedDataWriteException {
public void write(
TweedDataVisitor writer,
@Nullable Object value,
ConfigEntry<@Nullable Object> entry,
TweedWriteContext context
) throws TweedDataWriteException {
writer.visitNull();
}
@@ -326,11 +350,11 @@ public class TweedEntryReaderWriterImpls {
}
}
private interface PrimitiveReadFunction<T extends Object> {
private interface PrimitiveReadFunction<T> {
T read(TweedDataToken token) throws TweedDataReadException;
}
private interface PrimitiveWriteFunction<T extends Object> {
private interface PrimitiveWriteFunction<T> {
void write(TweedDataVisitor writer, T value) throws TweedDataWriteException;
}
@@ -363,4 +387,44 @@ public class TweedEntryReaderWriterImpls {
return TweedReadResult.failed(TweedReadIssue.error(e, context));
}
}
private static <T extends @Nullable Object> void visitStructuredEntryShallow(
StructuredConfigEntry<T> entry,
T value,
ShallowValueVisitor shallowValueVisitor
) throws TweedDataWriteException, TweedEntryWriteException {
entry.visitInOrder(new ConfigEntryValueVisitor() {
private @Nullable SubEntryKey currentSubEntryKey;
@Override
public <U> boolean enterStructuredEntry(StructuredConfigEntry<U> vEntry, U vValue) {
if (entry == vEntry) {
return true;
}
visitEntry(vEntry, vValue);
return false;
}
@Override
public boolean enterSubEntry(SubEntryKey subEntryKey) {
currentSubEntryKey = subEntryKey;
return true;
}
@Override
@SneakyThrows
public <U> void visitEntry(ConfigEntry<U> vEntry, U vValue) {
if (currentSubEntryKey != null) {
shallowValueVisitor.visit(vEntry, currentSubEntryKey, vValue);
currentSubEntryKey = null;
}
}
}, value);
}
@FunctionalInterface
private interface ShallowValueVisitor {
<T extends @Nullable Object> void visit(ConfigEntry<T> entry, SubEntryKey key, T value)
throws TweedDataWriteException, TweedEntryWriteException;
}
}

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.serde.extension.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
import de.siphalor.tweed5.serde.extension.api.*;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.patchwork.api.Patchwork;
@@ -20,14 +21,14 @@ class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
@Override
public <T extends @Nullable Object, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry(
TweedDataReader reader, C entry
TweedDataReader reader, C entry, SubEntryKey key
) {
return readWriteExtension.getReaderChain(entry).read(reader, entry, this);
}
@Override
public <T extends @Nullable Object, C extends ConfigEntry<T>> void writeSubEntry(
TweedDataVisitor writer, @Nullable T value, C entry
TweedDataVisitor writer, C entry, SubEntryKey key, @Nullable T value
) throws TweedEntryWriteException, TweedDataWriteException {
readWriteExtension.getWriterChain(entry).write(writer, value, entry, this);
}

View File

@@ -80,19 +80,21 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterStructuredEntry(this, value)) {
subEntries.forEach((key, entry) -> {
for (Map.Entry<String, SubEntry> entry : subEntries.entrySet()) {
String key = entry.getKey();
SubEntryKey subEntryKey = SubEntryKey.addressable(key, key, key);
if (visitor.enterSubEntry(subEntryKey)) {
Object subValue;
try {
Object subValue = entry.getter().invoke(value);
//noinspection unchecked
((ConfigEntry<Object>) entry.configEntry()).visitInOrder(visitor, subValue);
visitor.leaveSubEntry(subEntryKey);
subValue = entry.getValue().getter().invoke(value);
} catch (Throwable e) {
throw new RuntimeException("Failed to get compound sub entry value \"" + key + "\"");
}
//noinspection unchecked
((ConfigEntry<Object>) entry.getValue().configEntry()).visitInOrder(visitor, subValue);
visitor.leaveSubEntry(subEntryKey);
}
}
});
visitor.leaveStructuredEntry(this, value);
}
}