From 3c39848b5d220857b35e35b84301eee02c88dfda Mon Sep 17 00:00:00 2001 From: Siphalor Date: Mon, 20 Apr 2026 13:46:39 +0200 Subject: [PATCH] refactor!(core, serde-extension): Extend entry serde to make use of SubEntryKey for structured entries --- .../bridge/impl/TweedCoatMappersImpl.java | 4 +- .../core/api/entry/CollectionConfigEntry.java | 9 +- .../core/api/entry/NullableConfigEntry.java | 1 + .../serde/extension/api/TweedReadContext.java | 3 +- .../extension/api/TweedWriteContext.java | 3 +- .../impl/TweedEntryReaderWriterImpls.java | 122 +++++++++++++----- .../impl/TweedReadWriteContextImpl.java | 5 +- .../entry/StaticPojoCompoundConfigEntry.java | 14 +- 8 files changed, 118 insertions(+), 43 deletions(-) diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java index fc1e7ea..b786210 100644 --- a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java @@ -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 > TweedReadResult readSubEntry( TweedDataReader reader, - C entry + C entry, + SubEntryKey key ) { return TweedReadResult.empty(); } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java index 92d59d7..988989f 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java @@ -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> extends StructuredConfigEntry { + String ELEMENT_KEY = "element"; + @Override default CollectionConfigEntry apply(Consumer> function) { StructuredConfigEntry.super.apply(function); @@ -37,7 +40,7 @@ public interface CollectionConfigEntry> 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> extends Struc @Override default Map> subEntries() { - return Collections.singletonMap("element", elementEntry()); + return Collections.singletonMap(ELEMENT_KEY, elementEntry()); } ConfigEntry elementEntry(); - T instantiateCollection(int size); + @NonNull T instantiateCollection(int size); } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java index e62e31b..bcac983 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java @@ -7,6 +7,7 @@ import java.util.function.Consumer; public interface NullableConfigEntry extends AddressableStructuredConfigEntry { String NON_NULL_KEY = ":nonNull"; + SubEntryKey NON_NULL_SUB_ENTRY_KEY = SubEntryKey.transparentAddressable(NON_NULL_KEY, NON_NULL_KEY); @Override default NullableConfigEntry apply(Consumer> function) { diff --git a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedReadContext.java b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedReadContext.java index 78b5085..3d252b7 100644 --- a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedReadContext.java +++ b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedReadContext.java @@ -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(); > TweedReadResult readSubEntry( - TweedDataReader reader, C entry + TweedDataReader reader, C entry, SubEntryKey key ); } diff --git a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedWriteContext.java b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedWriteContext.java index 3da070e..be81833 100644 --- a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedWriteContext.java +++ b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/TweedWriteContext.java @@ -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(); > void writeSubEntry( - TweedDataVisitor writer, @Nullable T value, C entry + TweedDataVisitor writer, C entry, SubEntryKey key, @Nullable T value ) throws TweedEntryWriteException, TweedDataWriteException; } diff --git a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java index eb499e8..e6c388a 100644 --- a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java +++ b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java @@ -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> implements TweedEntryReaderWriter> { + public static class EnumReaderWriter> implements TweedEntryReaderWriter> { @Override public TweedReadResult read(TweedDataReader reader, ConfigEntry 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 entry, TweedWriteContext context ) throws TweedEntryWriteException, TweedDataWriteException { @@ -149,7 +147,7 @@ public class TweedEntryReaderWriterImpls { } } - public static class CollectionReaderWriter> implements TweedEntryReaderWriter> { + public static class CollectionReaderWriter> implements TweedEntryReaderWriter> { @Override public TweedReadResult read(TweedDataReader reader, CollectionConfigEntry entry, TweedReadContext context) { return requireToken(reader, TweedDataToken::isListStart, "Expected list start", context).andThen(_token -> { @@ -160,6 +158,7 @@ public class TweedEntryReaderWriterImpls { ConfigEntry elementEntry = entry.elementEntry(); List list = new ArrayList<>(20); List 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 elementResult = context.readSubEntry(reader, elementEntry); + TweedReadResult 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 elementEntry = entry.elementEntry(); - writer.visitListStart(); - for (T element : value) { - context.writeSubEntry(writer, element, elementEntry); - } + + visitStructuredEntryShallow(entry, value, new ShallowValueVisitor() { + @Override + public void visit(ConfigEntry 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 subEntryResult = context.readSubEntry(reader, subEntry); + + TweedReadResult 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> compoundEntries = (Map>)(Map) entry.subEntries(); - for (Map.Entry> e : compoundEntries.entrySet()) { - String key = e.getKey(); - ConfigEntry subEntry = e.getValue(); - - writer.visitMapEntryKey(key); - context.writeSubEntry(writer, entry.get(value, key), subEntry); - } + visitStructuredEntryShallow(entry, value, new ShallowValueVisitor() { + @Override + public void visit(ConfigEntry 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> { @Override - public TweedReadResult<@Nullable Object> read(TweedDataReader reader, ConfigEntry 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 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 { + private interface PrimitiveReadFunction { T read(TweedDataToken token) throws TweedDataReadException; } - private interface PrimitiveWriteFunction { + private interface PrimitiveWriteFunction { void write(TweedDataVisitor writer, T value) throws TweedDataWriteException; } @@ -363,4 +387,44 @@ public class TweedEntryReaderWriterImpls { return TweedReadResult.failed(TweedReadIssue.error(e, context)); } } + + private static void visitStructuredEntryShallow( + StructuredConfigEntry entry, + T value, + ShallowValueVisitor shallowValueVisitor + ) throws TweedDataWriteException, TweedEntryWriteException { + entry.visitInOrder(new ConfigEntryValueVisitor() { + private @Nullable SubEntryKey currentSubEntryKey; + + @Override + public boolean enterStructuredEntry(StructuredConfigEntry 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 void visitEntry(ConfigEntry vEntry, U vValue) { + if (currentSubEntryKey != null) { + shallowValueVisitor.visit(vEntry, currentSubEntryKey, vValue); + currentSubEntryKey = null; + } + } + }, value); + } + + @FunctionalInterface + private interface ShallowValueVisitor { + void visit(ConfigEntry entry, SubEntryKey key, T value) + throws TweedDataWriteException, TweedEntryWriteException; + } } diff --git a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedReadWriteContextImpl.java b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedReadWriteContextImpl.java index 48e1e5e..bd01949 100644 --- a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedReadWriteContextImpl.java +++ b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedReadWriteContextImpl.java @@ -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 > TweedReadResult readSubEntry( - TweedDataReader reader, C entry + TweedDataReader reader, C entry, SubEntryKey key ) { return readWriteExtension.getReaderChain(entry).read(reader, entry, this); } @Override public > 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); } diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java index 99c9c1e..3dd0e2b 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java @@ -80,19 +80,21 @@ public class StaticPojoCompoundConfigEntry extends BaseConfigEntry impleme @Override public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { if (visitor.enterStructuredEntry(this, value)) { - subEntries.forEach((key, entry) -> { + for (Map.Entry 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) 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) entry.getValue().configEntry()).visitInOrder(visitor, subValue); + visitor.leaveSubEntry(subEntryKey); } - }); + } visitor.leaveStructuredEntry(this, value); } }