[build] Restructure to composite build

This commit is contained in:
2025-10-26 02:03:53 +02:00
parent 0e3990aed9
commit 1fbc97866c
348 changed files with 126 additions and 64 deletions

View File

@@ -0,0 +1,11 @@
plugins {
id("de.siphalor.tweed5.base-module")
}
dependencies {
api(project(":tweed5-core"))
api(project(":tweed5-patchwork"))
api(project(":tweed5-serde-api"))
testImplementation(project(":tweed5-serde-hjson"))
}

View File

@@ -0,0 +1,2 @@
module.name = Tweed 5 Serde Extension
module.description = A Tweed extension that provides support for reading and writing entries using Tweed's serde API.

View File

@@ -0,0 +1,130 @@
package de.siphalor.tweed5.data.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.data.extension.impl.ReadWriteExtensionImpl;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import org.jspecify.annotations.Nullable;
import java.util.function.Consumer;
import java.util.function.Function;
public interface ReadWriteExtension extends TweedExtension {
Class<? extends ReadWriteExtension> DEFAULT = ReadWriteExtensionImpl.class;
String EXTENSION_ID = "read-write";
@Override
default String getId() {
return EXTENSION_ID;
}
static <T> Consumer<ConfigEntry<T>> entryReaderWriter(
TweedEntryReaderWriter<T, ? extends ConfigEntry<T>> entryReaderWriter
) {
return entryReaderWriter(entryReaderWriter, entryReaderWriter);
}
static <T> Consumer<ConfigEntry<T>> entryReaderWriter(
TweedEntryReader<T, ? extends ConfigEntry<T>> entryReader,
TweedEntryWriter<T, ? extends ConfigEntry<T>> entryWriter
) {
return entry -> {
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
extension.setEntryReader(entry, entryReader);
extension.setEntryWriter(entry, entryWriter);
};
}
static <T> Consumer<ConfigEntry<T>> entryReader(TweedEntryReader<T, ? extends ConfigEntry<T>> entryReader) {
return entry -> {
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
extension.setEntryReader(entry, entryReader);
};
}
static <T> Consumer<ConfigEntry<T>> entryWriter(TweedEntryWriter<T, ? extends ConfigEntry<T>> entryWriter) {
return entry -> {
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
extension.setEntryWriter(entry, entryWriter);
};
}
static <T extends @Nullable Object> Function<ConfigEntry<T>, T> read(TweedDataReader reader) {
return read(reader, null);
}
static <T extends @Nullable Object> Function<ConfigEntry<T>, T> read(
TweedDataReader reader,
@Nullable Consumer<Patchwork> contextExtensionsDataCustomizer
) {
return entry -> {
try {
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
Patchwork contextExtensionsData = extension.createReadWriteContextExtensionsData();
if (contextExtensionsDataCustomizer != null) {
contextExtensionsDataCustomizer.accept(contextExtensionsData);
}
return extension.read(reader, entry, contextExtensionsData);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
static <T extends @Nullable Object> Consumer<ConfigEntry<T>> write(TweedDataVisitor writer, T value) {
return write(writer, value, null);
}
static <T extends @Nullable Object> Consumer<ConfigEntry<T>> write(
TweedDataVisitor writer,
T value,
@Nullable Consumer<Patchwork> contextExtensionsDataCustomizer
) {
return entry -> {
try {
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
Patchwork contextExtensionsData = extension.createReadWriteContextExtensionsData();
if (contextExtensionsDataCustomizer != null) {
contextExtensionsDataCustomizer.accept(contextExtensionsData);
}
extension.write(writer, value, entry, contextExtensionsData);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
<T, C extends ConfigEntry<T>> @Nullable TweedEntryReader<T, C> getDefinedEntryReader(ConfigEntry<T> entry);
<T, C extends ConfigEntry<T>> @Nullable TweedEntryWriter<T, C> getDefinedEntryWriter(ConfigEntry<T> entry);
<T, C extends ConfigEntry<T>> void setEntryReaderWriter(
ConfigEntry<T> entry,
TweedEntryReader<T, C> entryReader,
TweedEntryWriter<T, C> entryWriter
);
<T, C extends ConfigEntry<T>> void setEntryReader(ConfigEntry<T> entry, TweedEntryReader<T, C> entryReader);
<T, C extends ConfigEntry<T>> void setEntryWriter(ConfigEntry<T> entry, TweedEntryWriter<T, C> entryWriter);
Patchwork createReadWriteContextExtensionsData();
<T extends @Nullable Object> T read(TweedDataReader reader, ConfigEntry<T> entry, Patchwork contextExtensionsData)
throws TweedEntryReadException;
<T extends @Nullable Object> void write(
TweedDataVisitor writer,
T value,
ConfigEntry<T> entry,
Patchwork contextExtensionsData
) throws TweedEntryWriteException;
<T, C extends ConfigEntry<T>> TweedEntryReader<T, C> getReaderChain(C entry);
<T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> getWriterChain(C entry);
}

View File

@@ -0,0 +1,28 @@
package de.siphalor.tweed5.data.extension.api;
import lombok.Getter;
@Getter
public class TweedEntryReadException extends Exception {
private final TweedReadContext context;
public TweedEntryReadException(String message, TweedReadContext context) {
super(message);
this.context = context;
}
public TweedEntryReadException(String message, Throwable cause, TweedReadContext context) {
super(message, cause);
this.context = context;
}
public TweedEntryReadException(String message, TweedEntryReadException cause) {
super(message, cause);
this.context = cause.context;
}
public TweedEntryReadException(Throwable cause, TweedReadContext context) {
super(cause);
this.context = context;
}
}

View File

@@ -0,0 +1,10 @@
package de.siphalor.tweed5.data.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import org.jspecify.annotations.Nullable;
@FunctionalInterface
public interface TweedEntryReader<T extends @Nullable Object, C extends ConfigEntry<T>> {
T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException;
}

View File

@@ -0,0 +1,28 @@
package de.siphalor.tweed5.data.extension.api;
import lombok.Getter;
@Getter
public class TweedEntryWriteException extends Exception {
private final TweedWriteContext context;
public TweedEntryWriteException(String message, TweedWriteContext context) {
super(message);
this.context = context;
}
public TweedEntryWriteException(String message, Throwable cause, TweedWriteContext context) {
super(message, cause);
this.context = context;
}
public TweedEntryWriteException(String message, TweedEntryWriteException cause) {
super(message, cause);
this.context = cause.context;
}
public TweedEntryWriteException(Throwable cause, TweedWriteContext context) {
super(cause);
this.context = context;
}
}

View File

@@ -0,0 +1,12 @@
package de.siphalor.tweed5.data.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
import org.jspecify.annotations.Nullable;
@FunctionalInterface
public interface TweedEntryWriter<T extends @Nullable Object, C extends ConfigEntry<T>> {
void write(TweedDataVisitor writer, @Nullable T value, C entry, TweedWriteContext context)
throws TweedEntryWriteException, TweedDataWriteException;
}

View File

@@ -0,0 +1,8 @@
package de.siphalor.tweed5.data.extension.api;
import de.siphalor.tweed5.patchwork.api.Patchwork;
public interface TweedReadContext {
ReadWriteExtension readWriteExtension();
Patchwork extensionsData();
}

View File

@@ -0,0 +1,44 @@
package de.siphalor.tweed5.data.extension.api;
import lombok.RequiredArgsConstructor;
/**
* An interface that allows to register {@link TweedEntryReader}s and {@link TweedEntryWriter}s.
* Implementing classes should be Java services, e.g. using {@link com.google.auto.service.AutoService}.
*/
public interface TweedReaderWriterProvider {
void provideReaderWriters(ProviderContext context);
/**
* The context where reader and writer factories may be registered.<br />
* The reader and writer ids must be globally unique. It is therefore recommended to scope custom reader and writer ids in your own namespace (e.g. "de.siphalor.custom.blub")
* Ids may consist of alphanumeric characters and dots.
*/
interface ProviderContext {
void registerReaderFactory(String id, ReaderWriterFactory<TweedEntryReader<?, ?>> readerFactory);
void registerWriterFactory(String id, ReaderWriterFactory<TweedEntryWriter<?, ?>> writerFactory);
}
/**
* A factory that creates a new reader or writer using delegate readers/writers as its arguments.
* @param <T>
*/
@FunctionalInterface
interface ReaderWriterFactory<T> {
T create(T... delegateReaderWriters);
}
@RequiredArgsConstructor
final class StaticReaderWriterFactory<T> implements ReaderWriterFactory<T> {
private final T readerWriter;
@SafeVarargs
@Override
public final T create(T... delegateReaderWriters) {
if (delegateReaderWriters.length != 0) {
throw new IllegalArgumentException("Reader writer factory must not be passed any delegates as arguments");
}
return readerWriter;
}
}
}

View File

@@ -0,0 +1,8 @@
package de.siphalor.tweed5.data.extension.api;
import de.siphalor.tweed5.patchwork.api.Patchwork;
public interface TweedWriteContext {
ReadWriteExtension readWriteExtension();
Patchwork extensionsData();
}

View File

@@ -0,0 +1,12 @@
package de.siphalor.tweed5.data.extension.api.extension;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
public interface ReadWriteExtensionSetupContext {
<E> PatchworkPartAccess<E> registerReadWriteContextExtensionData(Class<E> extensionDataClass);
void registerReaderMiddleware(Middleware<TweedEntryReader<?, ?>> middleware);
void registerWriterMiddleware(Middleware<TweedEntryWriter<?, ?>> middleware);
}

View File

@@ -0,0 +1,6 @@
package de.siphalor.tweed5.data.extension.api.extension;
public interface ReadWriteRelatedExtension {
default void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
}
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphalor.tweed5.data.extension.api;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,8 @@
package de.siphalor.tweed5.data.extension.api.readwrite;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import org.jspecify.annotations.Nullable;
public interface TweedEntryReaderWriter<T extends @Nullable Object, C extends ConfigEntry<T>> extends TweedEntryReader<T, C>, TweedEntryWriter<T, C> {}

View File

@@ -0,0 +1,71 @@
package de.siphalor.tweed5.data.extension.api.readwrite;
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.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jspecify.annotations.NonNull;
import java.util.Collection;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TweedEntryReaderWriters {
public static TweedEntryReaderWriter<Boolean, ConfigEntry<Boolean>> booleanReaderWriter() {
return TweedEntryReaderWriterImpls.BOOLEAN_READER_WRITER;
}
public static TweedEntryReaderWriter<Byte, ConfigEntry<Byte>> byteReaderWriter() {
return TweedEntryReaderWriterImpls.BYTE_READER_WRITER;
}
public static TweedEntryReaderWriter<Short, ConfigEntry<Short>> shortReaderWriter() {
return TweedEntryReaderWriterImpls.SHORT_READER_WRITER;
}
public static TweedEntryReaderWriter<Integer, ConfigEntry<Integer>> intReaderWriter() {
return TweedEntryReaderWriterImpls.INT_READER_WRITER;
}
public static TweedEntryReaderWriter<Long, ConfigEntry<Long>> longReaderWriter() {
return TweedEntryReaderWriterImpls.LONG_READER_WRITER;
}
public static TweedEntryReaderWriter<Float, ConfigEntry<Float>> floatReaderWriter() {
return TweedEntryReaderWriterImpls.FLOAT_READER_WRITER;
}
public static TweedEntryReaderWriter<Double, ConfigEntry<Double>> doubleReaderWriter() {
return TweedEntryReaderWriterImpls.DOUBLE_READER_WRITER;
}
public static TweedEntryReaderWriter<String, ConfigEntry<String>> stringReaderWriter() {
return TweedEntryReaderWriterImpls.STRING_READER_WRITER;
}
public static <T extends Enum<T>> TweedEntryReaderWriter<T, ConfigEntry<T>> enumReaderWriter() {
//noinspection unchecked,rawtypes
return (TweedEntryReaderWriter<T, @NonNull ConfigEntry<T>>)(TweedEntryReaderWriter) TweedEntryReaderWriterImpls.ENUM_READER_WRITER;
}
public static <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> nullableReader(TweedEntryReader<T, C> delegate) {
return new TweedEntryReaderWriterImpls.NullableReader<>(delegate);
}
public static <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> nullableWriter(TweedEntryWriter<T, C> delegate) {
return new TweedEntryReaderWriterImpls.NullableWriter<>(delegate);
}
public static <T, C extends Collection<T>> TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> collectionReaderWriter() {
//noinspection unchecked
return (TweedEntryReaderWriter<C, @NonNull CollectionConfigEntry<T, C>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COLLECTION_READER_WRITER;
}
public static <T> TweedEntryReaderWriter<T, CompoundConfigEntry<T>> compoundReaderWriter() {
//noinspection unchecked
return (TweedEntryReaderWriter<T, @NonNull CompoundConfigEntry<T>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COMPOUND_READER_WRITER;
}
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphalor.tweed5.data.extension.api.readwrite;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,60 @@
package de.siphalor.tweed5.data.extension.impl;
import com.google.auto.service.AutoService;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
@AutoService(TweedReaderWriterProvider.class)
public class DefaultTweedEntryReaderWriterImplsProvider implements TweedReaderWriterProvider {
@Override
public void provideReaderWriters(ProviderContext context) {
StaticReaderWriterFactory<TweedEntryReader<?, ?>> booleanReaderFactory = new StaticReaderWriterFactory<>(booleanReaderWriter());
StaticReaderWriterFactory<TweedEntryWriter<?, ?>> booleanWriterFactory = new StaticReaderWriterFactory<>(booleanReaderWriter());
context.registerReaderFactory("tweed5.bool", booleanReaderFactory);
context.registerReaderFactory("tweed5.boolean", booleanReaderFactory);
context.registerWriterFactory("tweed5.bool", booleanWriterFactory);
context.registerWriterFactory("tweed5.boolean", booleanWriterFactory);
context.registerReaderFactory("tweed5.byte", new StaticReaderWriterFactory<>(byteReaderWriter()));
context.registerWriterFactory("tweed5.byte", new StaticReaderWriterFactory<>(byteReaderWriter()));
context.registerReaderFactory("tweed5.short", new StaticReaderWriterFactory<>(shortReaderWriter()));
context.registerWriterFactory("tweed5.short", new StaticReaderWriterFactory<>(shortReaderWriter()));
StaticReaderWriterFactory<TweedEntryReader<?, ?>> integerReaderFactory =
new StaticReaderWriterFactory<>(intReaderWriter());
StaticReaderWriterFactory<TweedEntryWriter<?, ?>> integerWriterFactory =
new StaticReaderWriterFactory<>(intReaderWriter());
context.registerReaderFactory("tweed5.int", integerReaderFactory);
context.registerReaderFactory("tweed5.integer", integerReaderFactory);
context.registerWriterFactory("tweed5.int", integerWriterFactory);
context.registerWriterFactory("tweed5.integer", integerWriterFactory);
context.registerReaderFactory("tweed5.long", new StaticReaderWriterFactory<>(longReaderWriter()));
context.registerWriterFactory("tweed5.long", new StaticReaderWriterFactory<>(longReaderWriter()));
context.registerReaderFactory("tweed5.float", new StaticReaderWriterFactory<>(floatReaderWriter()));
context.registerWriterFactory("tweed5.float", new StaticReaderWriterFactory<>(floatReaderWriter()));
context.registerReaderFactory("tweed5.double", new StaticReaderWriterFactory<>(doubleReaderWriter()));
context.registerWriterFactory("tweed5.double", new StaticReaderWriterFactory<>(doubleReaderWriter()));
context.registerReaderFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
context.registerWriterFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
context.registerReaderFactory("tweed5.enum", new StaticReaderWriterFactory<>(enumReaderWriter()));
context.registerWriterFactory("tweed5.enum", new StaticReaderWriterFactory<>(enumReaderWriter()));
context.registerReaderFactory("tweed5.collection", new StaticReaderWriterFactory<>(collectionReaderWriter()));
context.registerWriterFactory("tweed5.collection", new StaticReaderWriterFactory<>(collectionReaderWriter()));
context.registerReaderFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
context.registerWriterFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
context.registerReaderFactory("tweed5.nullable", delegateReaders -> {
if (delegateReaders.length != 1) {
throw new IllegalArgumentException("Nullable reader requires a single delegate argument, got " + delegateReaders.length);
}
return nullableReader(delegateReaders[0]);
});
context.registerWriterFactory("tweed5.nullable", delegateWriters -> {
if (delegateWriters.length != 1) {
throw new IllegalArgumentException("Nullable writer requires a single delegate argument, got " + delegateWriters.length);
}
return nullableWriter(delegateWriters[0]);
});
}
}

View File

@@ -0,0 +1,184 @@
package de.siphalor.tweed5.data.extension.impl;
import com.google.auto.service.AutoService;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.*;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.Data;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
@AutoService(ReadWriteExtension.class)
public class ReadWriteExtensionImpl implements ReadWriteExtension {
private final ConfigContainer<?> configContainer;
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>>
entryReaderMiddlewareContainer
= new DefaultMiddlewareContainer<>();
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>>
entryWriterMiddlewareContainer
= new DefaultMiddlewareContainer<>();
private @Nullable PatchworkFactory readWriteContextPatchworkFactory;
public ReadWriteExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
this.configContainer = configContainer;
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
}
@Override
public void extensionsFinalized() {
Collection<TweedExtension> extensions = configContainer.extensions();
PatchworkFactory.Builder readWriteContextPatchworkFactorBuilder = PatchworkFactory.builder();
entryReaderMiddlewareContainer = new DefaultMiddlewareContainer<>();
entryWriterMiddlewareContainer = new DefaultMiddlewareContainer<>();
ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() {
@Override
public <E> PatchworkPartAccess<E> registerReadWriteContextExtensionData(Class<E> extensionDataClass) {
return readWriteContextPatchworkFactorBuilder.registerPart(extensionDataClass);
}
@Override
public void registerReaderMiddleware(Middleware<TweedEntryReader<?, ?>> middleware) {
entryReaderMiddlewareContainer.register(middleware);
}
@Override
public void registerWriterMiddleware(Middleware<TweedEntryWriter<?, ?>> middleware) {
entryWriterMiddlewareContainer.register(middleware);
}
};
for (TweedExtension extension : extensions) {
if (extension instanceof ReadWriteRelatedExtension) {
((ReadWriteRelatedExtension) extension).setupReadWriteExtension(setupContext);
}
}
readWriteContextPatchworkFactory = readWriteContextPatchworkFactorBuilder.build();
entryReaderMiddlewareContainer.seal();
entryWriterMiddlewareContainer.seal();
}
@Override
public @Nullable <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> getDefinedEntryReader(ConfigEntry<T> entry) {
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
if (customEntryData == null) {
return null;
}
//noinspection unchecked
return (TweedEntryReader<T, C>) customEntryData.readerDefinition();
}
@Override
public @Nullable <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> getDefinedEntryWriter(ConfigEntry<T> entry) {
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
if (customEntryData == null) {
return null;
}
//noinspection unchecked
return (TweedEntryWriter<T, C>) customEntryData.writerDefinition();
}
@Override
public <T, C extends ConfigEntry<T>> void setEntryReaderWriter(
ConfigEntry<T> entry,
TweedEntryReader<T, C> entryReader,
TweedEntryWriter<T, C> entryWriter
) {
CustomEntryData customEntryData = getOrCreateCustomEntryData(entry);
customEntryData.readerDefinition(entryReader);
customEntryData.writerDefinition(entryWriter);
}
@Override
public <T, C extends ConfigEntry<T>> void setEntryReader(ConfigEntry<T> entry, TweedEntryReader<T, C> entryReader) {
getOrCreateCustomEntryData(entry).readerDefinition(entryReader);
}
@Override
public <T, C extends ConfigEntry<T>> void setEntryWriter(ConfigEntry<T> entry, TweedEntryWriter<T, C> entryWriter) {
getOrCreateCustomEntryData(entry).writerDefinition(entryWriter);
}
@Override
public void initEntry(ConfigEntry<?> configEntry) {
CustomEntryData customEntryData = getOrCreateCustomEntryData(configEntry);
customEntryData.readerChain(entryReaderMiddlewareContainer.process(customEntryData.readerDefinition()));
customEntryData.writerChain(entryWriterMiddlewareContainer.process(customEntryData.writerDefinition()));
}
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
if (entryData == null) {
entryData = new CustomEntryData();
entry.extensionsData().set(customEntryDataAccess, entryData);
}
return entryData;
}
@Override
public Patchwork createReadWriteContextExtensionsData() {
assert readWriteContextPatchworkFactory != null;
return readWriteContextPatchworkFactory.create();
}
public <T extends @Nullable Object> T read(
TweedDataReader reader,
ConfigEntry<T> entry,
Patchwork contextExtensionsData
) throws TweedEntryReadException {
TweedReadContext context = new TweedReadWriteContextImpl(this, contextExtensionsData);
return getReaderChain(entry).read(reader, entry, context);
}
@Override
public <T extends @Nullable Object> void write(
TweedDataVisitor writer,
@Nullable T value,
ConfigEntry<T> entry,
Patchwork contextExtensionsData
) throws TweedEntryWriteException {
TweedWriteContext context = new TweedReadWriteContextImpl(this, contextExtensionsData);
try {
getWriterChain(entry).write(writer, value, entry, context);
} catch (TweedDataWriteException e) {
throw new TweedEntryWriteException("Failed to write entry", e, context);
}
}
@Data
private static class CustomEntryData {
private TweedEntryReader<?, ?> readerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
private TweedEntryWriter<?, ?> writerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
private TweedEntryReader<?, ?> readerChain;
private TweedEntryWriter<?, ?> writerChain;
}
@Override
public <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> getReaderChain(C entry) {
//noinspection unchecked
return (TweedEntryReader<T, C>) entry.extensionsData().get(customEntryDataAccess).readerChain();
}
@Override
public <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> getWriterChain(C entry) {
//noinspection unchecked
return (TweedEntryWriter<T, C>) entry.extensionsData().get(customEntryDataAccess).writerChain();
}
}

View File

@@ -0,0 +1,327 @@
package de.siphalor.tweed5.data.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.data.extension.api.*;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.dataapi.api.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TweedEntryReaderWriterImpls {
public static final TweedEntryReaderWriter<Boolean, ConfigEntry<Boolean>> BOOLEAN_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsBoolean, TweedDataVisitor::visitBoolean);
public static final TweedEntryReaderWriter<Byte, ConfigEntry<Byte>> BYTE_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsByte, TweedDataVisitor::visitByte);
public static final TweedEntryReaderWriter<Short, ConfigEntry<Short>> SHORT_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsShort, TweedDataVisitor::visitShort);
public static final TweedEntryReaderWriter<Integer, ConfigEntry<Integer>> INT_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsInt, TweedDataVisitor::visitInt);
public static final TweedEntryReaderWriter<Long, ConfigEntry<Long>> LONG_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsLong, TweedDataVisitor::visitLong);
public static final TweedEntryReaderWriter<Float, ConfigEntry<Float>> FLOAT_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsFloat, TweedDataVisitor::visitFloat);
public static final TweedEntryReaderWriter<Double, ConfigEntry<Double>> DOUBLE_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsDouble, TweedDataVisitor::visitDouble);
public static final TweedEntryReaderWriter<String, ConfigEntry<String>> STRING_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsString, TweedDataVisitor::visitString);
public static final TweedEntryReaderWriter<Enum<?>, ConfigEntry<Enum<?>>> ENUM_READER_WRITER = new EnumReaderWriter<>();
public static final TweedEntryReaderWriter<Collection<Object>, CollectionConfigEntry<Object, Collection<Object>>> COLLECTION_READER_WRITER = new CollectionReaderWriter<>();
public static final TweedEntryReaderWriter<Object, CompoundConfigEntry<Object>> COMPOUND_READER_WRITER = new CompoundReaderWriter<>();
public static final TweedEntryReaderWriter<Object, ConfigEntry<Object>> NOOP_READER_WRITER = new NoopReaderWriter();
@RequiredArgsConstructor
public static class NullableReader<T extends @Nullable Object, C extends ConfigEntry<T>> implements TweedEntryReader<T, C> {
private final TweedEntryReader<T, C> delegate;
@Override
public T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException {
try {
if (reader.peekToken().isNull()) {
reader.readToken();
return null;
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context);
}
return delegate.read(reader, entry, context);
}
}
@RequiredArgsConstructor
public static class NullableWriter<T extends @Nullable Object, C extends ConfigEntry<T>> implements TweedEntryWriter<T, C> {
private final TweedEntryWriter<T, C> delegate;
@Override
public void write(TweedDataVisitor writer, T value, C entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
if (value == null) {
writer.visitNull();
} else {
delegate.write(writer, value, entry, context);
}
}
}
@RequiredArgsConstructor
private static class PrimitiveReaderWriter<T> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
private final PrimitiveReadFunction<T> readerFunction;
private final PrimitiveWriteFunction<T> writerFunction;
@Override
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context)
throws TweedEntryReadException {
try {
return readerFunction.read(reader.readToken());
} catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context);
}
}
@Override
public void write(TweedDataVisitor writer, @Nullable T value, ConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
requireNonNullWriteValue(value, context);
writerFunction.write(writer, value);
}
}
@RequiredArgsConstructor
public static class EnumReaderWriter<T extends Enum<?>> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
@Override
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) throws
TweedEntryReadException {
try {
TweedDataToken token = reader.readToken();
assertIsToken(token, TweedDataToken::canReadAsString, "Expected string", context);
//noinspection unchecked,rawtypes
return (T) Enum.valueOf(((Class) entry.valueClass()), token.readAsString());
} catch (TweedDataReadException | IllegalArgumentException e) {
throw new TweedEntryReadException(
"Failed reading enum value for " + entry.valueClass().getName(),
e,
context
);
}
}
@Override
public void write(
TweedDataVisitor writer,
@Nullable T value,
ConfigEntry<T> entry,
TweedWriteContext context
) throws TweedEntryWriteException, TweedDataWriteException {
requireNonNullWriteValue(value, context);
writer.visitString(value.name());
}
}
public static class CollectionReaderWriter<T extends @Nullable Object, C extends Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> {
@Override
public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws
TweedEntryReadException {
TweedDataToken token;
try {
assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start", context);
token = reader.peekToken();
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading collection start", e, context);
}
if (token.isListEnd()) {
return entry.instantiateCollection(0);
}
ConfigEntry<T> elementEntry = entry.elementEntry();
TweedEntryReader<T, ConfigEntry<T>> elementReader = context.readWriteExtension().getReaderChain(elementEntry);
List<@Nullable T> list = new ArrayList<>(20);
while (true) {
try {
token = reader.peekToken();
if (token.isListEnd()) {
reader.readToken();
break;
} else if (token.isListValue()) {
list.add(elementReader.read(reader, elementEntry, context));
} else {
throw new TweedEntryReadException(
"Unexpected token " + token + ": expected next list value or list end",
context
);
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException(
"Failed reading element " + list.size() + " of collection",
e,
context
);
}
}
C result = entry.instantiateCollection(list.size());
result.addAll(list);
return result;
}
@Override
public void write(TweedDataVisitor writer, C value, CollectionConfigEntry<T, C> entry, TweedWriteContext context)
throws TweedEntryWriteException, TweedDataWriteException {
requireNonNullWriteValue(value, context);
if (value.isEmpty()) {
writer.visitEmptyList();
return;
}
ConfigEntry<T> elementEntry = entry.elementEntry();
TweedEntryWriter<T, ConfigEntry<T>> elementWriter = context.readWriteExtension().getWriterChain(elementEntry);
writer.visitListStart();
for (T element : value) {
elementWriter.write(writer, element, elementEntry, context);
}
writer.visitListEnd();
}
}
public static class CompoundReaderWriter<T> implements TweedEntryReaderWriter<T, CompoundConfigEntry<T>> {
@Override
public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws
TweedEntryReadException {
try {
assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start", context);
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading compound start", e, context);
}
Map<String, ConfigEntry<?>> compoundEntries = entry.subEntries();
T compoundValue = entry.instantiateCompoundValue();
while (true) {
try {
TweedDataToken token = reader.readToken();
if (token.isMapEnd()) {
break;
} else if (token.isMapEntryKey()) {
String key = token.readAsString();
//noinspection unchecked
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
if (subEntry == null) {
//noinspection DataFlowIssue
NOOP_READER_WRITER.read(reader, null, context);
continue;
}
val subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry);
Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context);
entry.set(compoundValue, key, subEntryValue);
} else {
throw new TweedEntryReadException(
"Unexpected token " + token + ": Expected map key or map end",
context
);
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading compound entry", e, context);
}
}
return compoundValue;
}
@Override
public void write(TweedDataVisitor writer, @Nullable T value, CompoundConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
requireNonNullWriteValue(value, context);
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();
TweedEntryWriter<Object, ConfigEntry<Object>> subEntryWriterChain = context.readWriteExtension().getWriterChain(subEntry);
writer.visitMapEntryKey(key);
subEntryWriterChain.write(writer, entry.get(value, key), subEntry, context);
}
writer.visitMapEnd();
}
}
public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry<Object>> {
@Override
public @Nullable Object read(TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context)
throws TweedEntryReadException {
try {
TweedDataToken token = reader.readToken();
if (!token.isListStart() && !token.isMapStart()) {
return null;
}
ArrayDeque<Context> stack = new ArrayDeque<>(20);
if (token.isListStart()) {
stack.push(Context.LIST);
} else if (token.isMapStart()) {
stack.push(Context.MAP);
}
while (true) {
token = reader.readToken();
if (token.isListStart()) {
stack.push(Context.LIST);
} else if (token.isMapStart()) {
stack.push(Context.MAP);
} else if (token.isListEnd() || token.isMapEnd()) {
stack.pop();
}
if (stack.isEmpty()) {
return null;
}
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed skipping through value", e, context);
}
}
@Override
public void write(TweedDataVisitor writer, @Nullable Object value, ConfigEntry<Object> entry, TweedWriteContext context) throws TweedDataWriteException {
writer.visitNull();
}
private enum Context {
LIST, MAP,
}
}
private interface PrimitiveReadFunction<T extends Object> {
T read(TweedDataToken token) throws TweedDataReadException;
}
private interface PrimitiveWriteFunction<T extends Object> {
void write(TweedDataVisitor writer, T value) throws TweedDataWriteException;
}
@Contract("null, _ -> fail")
private static <T> void requireNonNullWriteValue(
@Nullable T value,
TweedWriteContext context
) throws TweedEntryWriteException {
if (value == null) {
throw new TweedEntryWriteException("Unable to write null value", context);
}
}
private static void assertIsToken(
TweedDataToken token,
Predicate<TweedDataToken> isToken,
String description,
TweedReadContext context
) throws TweedEntryReadException {
if (!isToken.test(token)) {
throw new TweedEntryReadException("Unexpected token " + token + ": " + description, context);
}
}
}

View File

@@ -0,0 +1,13 @@
package de.siphalor.tweed5.data.extension.impl;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import lombok.Value;
@Value
public class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
ReadWriteExtension readWriteExtension;
Patchwork extensionsData;
}

View File

@@ -0,0 +1,6 @@
@ApiStatus.Internal
@NullMarked
package de.siphalor.tweed5.data.extension.impl;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,123 @@
package de.siphalor.tweed5.data.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.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.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
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.function.Function;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ReadWriteExtensionImplTest {
private final StringWriter stringWriter = new StringWriter();
private final ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
private CompoundConfigEntry<Map<String, Object>> rootEntry;
@SuppressWarnings("unchecked")
@BeforeEach
void setUp() {
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
configContainer.finishExtensionSetup();
SimpleConfigEntry<Integer> intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
.apply(entryReaderWriter(intReaderWriter()));
CollectionConfigEntry<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>(
configContainer,
(Class<List<Boolean>>) (Class<?>) List.class,
ArrayList::new,
new SimpleConfigEntryImpl<>(configContainer, Boolean.class)
.apply(entryReaderWriter(booleanReaderWriter()))
).apply(entryReaderWriter(collectionReaderWriter()));
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
((Class<Map<String, Object>>) (Class<?>) Map.class),
LinkedHashMap::new,
sequencedMap(List.of(
entry("int", intEntry),
entry("list", listEntry)
))
).apply(entryReaderWriter(compoundReaderWriter()));
configContainer.attachTree(rootEntry);
configContainer.initialize();
}
@Test
void write() {
Map<String, Object> value = new HashMap<>();
value.put("int", 123);
value.put("list", Arrays.asList(true, false, true));
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
assertDoesNotThrow(() -> readWriteExtension.write(
setupWriter(writer -> new HjsonWriter(writer, new HjsonWriter.Options())),
value,
rootEntry,
readWriteExtension.createReadWriteContextExtensionsData()
));
assertThat(stringWriter.toString()).isEqualTo("""
{
\tint: 123
\tlist: [
\t\ttrue
\t\tfalse
\t\ttrue
\t]
}
"""
);
}
@Test
void read() {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
Map<String, Object> result = assertDoesNotThrow(() -> readWriteExtension.read(
new HjsonReader(new HjsonLexer(new StringReader("""
{
\tint: 123
\tlist: [
\t\ttrue
\t\tfalse
\t\ttrue
\t]
}
"""))),
rootEntry,
readWriteExtension.createReadWriteContextExtensionsData()
));
assertEquals(2, result.size());
assertEquals(123, result.get("int"));
assertEquals(Arrays.asList(true, false, true), result.get("list"));
}
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {
return writerFactory.apply(stringWriter);
}
}