refactor(serde-ext, default-ext): Move path tracking into serde extension

This commit is contained in:
2026-05-24 16:45:59 +02:00
parent e5b433045f
commit f22c359a1c
13 changed files with 119 additions and 144 deletions
@@ -1,10 +1,11 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.TweedWriteContext;
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
@Deprecated
public interface PatherExtension extends TweedExtension {
Class<? extends PatherExtension> DEFAULT = PatherExtensionImpl.class;
String EXTENSION_ID = "pather";
@@ -1,124 +1,17 @@
package de.siphalor.tweed5.defaultextensions.pather.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.serde.extension.api.*;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.extension.ReaderMiddlewareContext;
import de.siphalor.tweed5.serde.extension.api.extension.WriterMiddlewareContext;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataReader;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.val;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExtension {
private @Nullable PatchworkPartAccess<PathTracking> rwContextPathTrackingAccess;
@Override
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
rwContextPathTrackingAccess = context.registerReadWriteContextExtensionData(PathTracking.class);
context.registerReaderMiddleware(createEntryReaderMiddleware());
context.registerWriterMiddleware(createEntryWriterMiddleware());
}
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.TweedWriteContext;
public class PatherExtensionImpl implements PatherExtension {
@Override
public String getPath(TweedReadContext context) {
assert rwContextPathTrackingAccess != null;
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking == null) {
throw new IllegalStateException("Path tracking is not active!");
}
return pathTracking.currentPath();
return context.currentEntryPath().toString();
}
@Override
public String getPath(TweedWriteContext context) {
assert rwContextPathTrackingAccess != null;
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking == null) {
throw new IllegalStateException("Path tracking is not active!");
}
return pathTracking.currentPath();
}
private Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext> createEntryReaderMiddleware() {
return new Middleware<TweedEntryReader<?, ?>, ReaderMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
assert rwContextPathTrackingAccess != null;
//noinspection unchecked
val castedInner = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext readContext) -> {
PathTracking pathTracking = readContext.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking != null) {
return castedInner.read(reader, entry, readContext);
}
pathTracking = PathTracking.create();
readContext.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, readContext);
};
}
};
}
private Middleware<TweedEntryWriter<?, ?>, WriterMiddlewareContext> createEntryWriterMiddleware() {
return new Middleware<TweedEntryWriter<?, ?>, WriterMiddlewareContext>() {
@Override
public String id() {
return EXTENSION_ID;
}
@Override
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner, WriterMiddlewareContext context) {
assert rwContextPathTrackingAccess != null;
//noinspection unchecked
val castedInner = (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext writeContext) -> {
PathTracking pathTracking = writeContext.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking != null) {
castedInner.write(writer, value, entry, writeContext);
return;
}
pathTracking = PathTracking.create();
writeContext.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
try {
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, writeContext);
} catch (TweedEntryWriteException e) {
PathTracking exceptionPathTracking =
e.context().extensionsData().get(rwContextPathTrackingAccess);
if (exceptionPathTracking != null) {
throw new TweedEntryWriteException(
"Exception while writing entry at "
+ exceptionPathTracking.currentPath()
+ ": " + e.getMessage(),
e
);
} else {
throw e;
}
}
};
}
};
return context.currentEntryPath().toString();
}
}
@@ -6,7 +6,6 @@ import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
@@ -39,7 +38,7 @@ public class ReadFallbackExtensionImpl implements ReadFallbackExtension, ReadWri
@Override
public Set<String> mustComeBefore() {
return Collections.singleton(PatherExtension.EXTENSION_ID);
return Collections.emptySet();
}
@Override
@@ -14,7 +14,6 @@ import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducerMiddlewareContext;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.pather.api.ValuePathTracking;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
@@ -35,7 +34,6 @@ import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import lombok.*;
import org.jetbrains.annotations.UnknownNullability;
import org.jspecify.annotations.Nullable;
import java.util.*;
@@ -77,12 +75,10 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
private final MiddlewareContainer<ConfigEntryValidator, ValidatorMiddlewareContext>
entryValidatorMiddlewareContainer = new DefaultMiddlewareContainer<>();
private @Nullable PatchworkPartAccess<ValidationIssues> readContextValidationIssuesAccess;
private @Nullable PatherExtension patherExtension;
public ValidationExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
this.configContainer = configContainer;
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
context.registerExtension(PatherExtension.class);
}
@Override
@@ -95,9 +91,6 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
}
entryValidatorMiddlewareContainer.seal();
patherExtension = configContainer.extension(PatherExtension.class)
.orElseThrow(() -> new IllegalStateException("Missing requested PatherExtension"));
}
@Override
@@ -235,14 +228,9 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
return EXTENSION_ID;
}
@Override
public Set<String> mustComeBefore() {
return Collections.singleton(PatherExtension.EXTENSION_ID);
}
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner, ReaderMiddlewareContext context) {
assert readContextValidationIssuesAccess != null && patherExtension != null;
assert readContextValidationIssuesAccess != null;
//noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
@@ -261,11 +249,10 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
return TweedReadResult.ok(validationResult.value());
}
String path = patherExtension.getPath(readContext);
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
entry,
validationResult.issues()
));
validationIssues.issuesByPath().put(
readContext.currentEntryPath().toString(),
new ValidationIssues.EntryIssues(entry, validationResult.issues())
);
if (validationResult.hasError()) {
return TweedReadResult.failed(
mapValidationIssuesToReadIssues(validationResult.issues(), readContext)
@@ -6,7 +6,6 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
@@ -77,7 +76,6 @@ class ReadFallbackExtensionImplTest {
void nestedWithPather(LogsCaptor<ReadFallbackExtensionImpl> logsCaptor) {
DefaultConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
configContainer.registerExtension(ReadWriteExtension.class);
configContainer.registerExtension(PatherExtension.class);
configContainer.registerExtension(PresetsExtension.class);
configContainer.registerExtension(ReadFallbackExtension.DEFAULT);
configContainer.finishExtensionSetup();
@@ -3,6 +3,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.path.EntryPath;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import org.jspecify.annotations.Nullable;
@@ -10,6 +11,8 @@ import org.jspecify.annotations.Nullable;
public interface TweedReadContext {
ReadWriteExtension readWriteExtension();
Patchwork extensionsData();
EntryPath currentEntryPath();
EntryPath currentValuePath();
<T extends @Nullable Object, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry(
TweedDataReader reader, C entry, SubEntryKey key
@@ -3,12 +3,15 @@ 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.path.EntryPath;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
import org.jspecify.annotations.Nullable;
public interface TweedWriteContext {
Patchwork extensionsData();
EntryPath currentEntryPath();
EntryPath currentValuePath();
<T extends @Nullable Object, C extends ConfigEntry<T>> void writeSubEntry(
TweedDataVisitor writer, C entry, SubEntryKey key, @Nullable T value
@@ -0,0 +1,19 @@
package de.siphalor.tweed5.serde.extension.api.path;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import java.util.List;
@RequiredArgsConstructor
public class EntryPath {
@Getter
private final List<@Nullable String> parts;
private final String string;
@Override
public String toString() {
return string;
}
}
@@ -89,4 +89,9 @@ public class TweedEntryReaderWriters {
//noinspection unchecked
return (TweedEntryReaderWriter<T, @NonNull CompoundConfigEntry<T>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COMPOUND_READER_WRITER;
}
public static <T extends @Nullable Object> TweedEntryReaderWriter<T, ConfigEntry<T>> noopReaderWriter() {
//noinspection unchecked
return (TweedEntryReaderWriter<T, ConfigEntry<T>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
}
}
@@ -3,8 +3,10 @@ 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.path.EntryPath;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.impl.path.ReadWritePathTracking;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
@@ -19,17 +21,44 @@ class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
@Getter
private final Patchwork extensionsData;
private final ReadWritePathTracking entryPathTracking = new ReadWritePathTracking();
private final ReadWritePathTracking valuePathTracking = new ReadWritePathTracking();
@Override
public EntryPath currentEntryPath() {
return new EntryPath(entryPathTracking.currentPathParts(), entryPathTracking.currentPath());
}
@Override
public EntryPath currentValuePath() {
return new EntryPath(valuePathTracking.currentPathParts(), valuePathTracking.currentPath());
}
@Override
public <T extends @Nullable Object, C extends ConfigEntry<T>> TweedReadResult<T> readSubEntry(
TweedDataReader reader, C entry, SubEntryKey key
) {
return readWriteExtension.getReaderChain(entry).read(reader, entry, this);
try {
entryPathTracking.push(key.entry());
valuePathTracking.push(key.value());
return readWriteExtension.getReaderChain(entry).read(reader, entry, this);
} finally {
entryPathTracking.pop();
valuePathTracking.pop();
}
}
@Override
public <T extends @Nullable Object, C extends ConfigEntry<T>> void writeSubEntry(
TweedDataVisitor writer, C entry, SubEntryKey key, @Nullable T value
) throws TweedEntryWriteException, TweedDataWriteException {
readWriteExtension.getWriterChain(entry).write(writer, value, entry, this);
try {
entryPathTracking.push(key.entry());
valuePathTracking.push(key.value());
readWriteExtension.getWriterChain(entry).write(writer, value, entry, this);
} finally {
entryPathTracking.pop();
valuePathTracking.pop();
}
}
}
@@ -0,0 +1,36 @@
package de.siphalor.tweed5.serde.extension.impl.path;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ReadWritePathTracking {
private final StringBuilder pathBuilder = new StringBuilder(256);
private final List<@Nullable String> pathParts = new ArrayList<>(50);
public void push(@Nullable String part) {
pathParts.add(part);
if (part != null) {
pathBuilder.append(".").append(part);
}
}
public void pop() {
if (!pathParts.isEmpty()) {
String poppedPart = pathParts.remove(pathParts.size() - 1);
if (poppedPart != null) {
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
}
}
}
public String currentPath() {
return pathBuilder.toString();
}
public List<@Nullable String> currentPathParts() {
return Collections.unmodifiableList(new ArrayList<>(pathParts));
}
}
@@ -3,30 +3,30 @@ package de.siphalor.tweed5.serde.extension.impl;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
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.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.entry.MutableStructuredConfigEntry;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde.hjson.HjsonWriter;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import de.siphalor.tweed5.testutils.generic.entry.TestMapConfigEntry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter;