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

View File

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

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.serde.extension.api; package de.siphalor.tweed5.serde.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; 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.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult; import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde_api.api.TweedDataReader;
@@ -11,6 +12,6 @@ public interface TweedReadContext {
Patchwork extensionsData(); Patchwork extensionsData();
<T extends @Nullable Object, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry( <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; package de.siphalor.tweed5.serde.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; 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.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor; import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException; import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
@@ -10,6 +11,6 @@ public interface TweedWriteContext {
Patchwork extensionsData(); Patchwork extensionsData();
<T extends @Nullable Object, C extends ConfigEntry<T>> void writeSubEntry( <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; ) throws TweedEntryWriteException, TweedDataWriteException;
} }

View File

@@ -1,9 +1,6 @@
package de.siphalor.tweed5.serde.extension.impl; package de.siphalor.tweed5.serde.extension.impl;
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry; import de.siphalor.tweed5.core.api.entry.*;
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.serde.extension.api.*; 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.ThrowingReadFunction;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue; 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.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@@ -50,7 +48,7 @@ public class TweedEntryReaderWriterImpls {
return TweedReadResult.failed(TweedReadIssue.error(e, context)); 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 @Override
@@ -63,7 +61,7 @@ public class TweedEntryReaderWriterImpls {
if (value == null) { if (value == null) {
writer.visitNull(); writer.visitNull();
} else { } 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 @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 @Override
public TweedReadResult<T> read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) { public TweedReadResult<T> read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) {
//noinspection unchecked,rawtypes //noinspection unchecked,rawtypes
@@ -140,7 +138,7 @@ public class TweedEntryReaderWriterImpls {
@Override @Override
public void write( public void write(
TweedDataVisitor writer, TweedDataVisitor writer,
@Nullable T value, T value,
ConfigEntry<T> entry, ConfigEntry<T> entry,
TweedWriteContext context TweedWriteContext context
) throws TweedEntryWriteException, TweedDataWriteException { ) 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 @Override
public TweedReadResult<C> read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) { public TweedReadResult<C> read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) {
return requireToken(reader, TweedDataToken::isListStart, "Expected list start", context).andThen(_token -> { return requireToken(reader, TweedDataToken::isListStart, "Expected list start", context).andThen(_token -> {
@@ -160,6 +158,7 @@ public class TweedEntryReaderWriterImpls {
ConfigEntry<T> elementEntry = entry.elementEntry(); ConfigEntry<T> elementEntry = entry.elementEntry();
List<T> list = new ArrayList<>(20); List<T> list = new ArrayList<>(20);
List<TweedReadIssue> issues = new ArrayList<>(); List<TweedReadIssue> issues = new ArrayList<>();
int i = 0;
while (true) { while (true) {
try { try {
TweedDataToken token = reader.peekToken(); TweedDataToken token = reader.peekToken();
@@ -167,7 +166,11 @@ public class TweedEntryReaderWriterImpls {
reader.readToken(); reader.readToken();
break; break;
} else if (token.isListValue()) { } 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())); issues.addAll(Arrays.asList(elementResult.issues()));
if (elementResult.isFailed() || elementResult.isError()) { if (elementResult.isFailed() || elementResult.isError()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0])); return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
@@ -184,6 +187,7 @@ public class TweedEntryReaderWriterImpls {
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
issues.add(TweedReadIssue.error(e, context)); issues.add(TweedReadIssue.error(e, context));
} }
i++;
} }
C result = entry.instantiateCollection(list.size()); C result = entry.instantiateCollection(list.size());
result.addAll(list); result.addAll(list);
@@ -201,12 +205,16 @@ public class TweedEntryReaderWriterImpls {
return; return;
} }
ConfigEntry<T> elementEntry = entry.elementEntry();
writer.visitListStart(); 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(); writer.visitListEnd();
} }
} }
@@ -236,7 +244,13 @@ public class TweedEntryReaderWriterImpls {
} }
continue; 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())); issues.addAll(Arrays.asList(subEntryResult.issues()));
if (subEntryResult.isFailed() || subEntryResult.isError()) { if (subEntryResult.isFailed() || subEntryResult.isError()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0])); return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
@@ -268,15 +282,16 @@ public class TweedEntryReaderWriterImpls {
writer.visitMapStart(); writer.visitMapStart();
//noinspection unchecked visitStructuredEntryShallow(entry, value, new ShallowValueVisitor() {
Map<String, ConfigEntry<Object>> compoundEntries = (Map<String, ConfigEntry<Object>>)(Map<?, ?>) entry.subEntries(); @Override
for (Map.Entry<String, ConfigEntry<Object>> e : compoundEntries.entrySet()) { public <U extends @Nullable Object> void visit(ConfigEntry<U> subEntry, SubEntryKey key, U subValue)
String key = e.getKey(); throws TweedDataWriteException, TweedEntryWriteException {
ConfigEntry<Object> subEntry = e.getValue(); if (key.value() != null) {
writer.visitMapEntryKey(key.value());
writer.visitMapEntryKey(key); context.writeSubEntry(writer, subEntry, key, subValue);
context.writeSubEntry(writer, entry.get(value, key), subEntry); }
} }
});
writer.visitMapEnd(); writer.visitMapEnd();
} }
@@ -284,7 +299,11 @@ public class TweedEntryReaderWriterImpls {
public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry<Object>> { public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry<Object>> {
@Override @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 { try {
TweedDataToken token = reader.readToken(); TweedDataToken token = reader.readToken();
if (!token.isListStart() && !token.isMapStart()) { if (!token.isListStart() && !token.isMapStart()) {
@@ -317,7 +336,12 @@ public class TweedEntryReaderWriterImpls {
} }
@Override @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(); 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; T read(TweedDataToken token) throws TweedDataReadException;
} }
private interface PrimitiveWriteFunction<T extends Object> { private interface PrimitiveWriteFunction<T> {
void write(TweedDataVisitor writer, T value) throws TweedDataWriteException; void write(TweedDataVisitor writer, T value) throws TweedDataWriteException;
} }
@@ -363,4 +387,44 @@ public class TweedEntryReaderWriterImpls {
return TweedReadResult.failed(TweedReadIssue.error(e, context)); 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; package de.siphalor.tweed5.serde.extension.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; 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.*;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult; import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
@@ -20,14 +21,14 @@ class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
@Override @Override
public <T extends @Nullable Object, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry( 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); return readWriteExtension.getReaderChain(entry).read(reader, entry, this);
} }
@Override @Override
public <T extends @Nullable Object, C extends ConfigEntry<T>> void writeSubEntry( 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 { ) throws TweedEntryWriteException, TweedDataWriteException {
readWriteExtension.getWriterChain(entry).write(writer, value, entry, this); readWriteExtension.getWriterChain(entry).write(writer, value, entry, this);
} }

View File

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