[core, weaver-pojo] Introduce NullableConfigEntry and pseudo entries during weaving
This commit is contained in:
@@ -2,7 +2,6 @@ package de.siphalor.tweed5.core.api.entry;
|
|||||||
|
|
||||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
import org.jspecify.annotations.NonNull;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -26,7 +25,7 @@ public interface ConfigEntry<T extends @Nullable Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void visitInOrder(ConfigEntryVisitor visitor);
|
void visitInOrder(ConfigEntryVisitor visitor);
|
||||||
void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value);
|
void visitInOrder(ConfigEntryValueVisitor visitor, T value);
|
||||||
|
|
||||||
T deepCopy(@NonNull T value);
|
T deepCopy(T value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public interface NullableConfigEntry<T extends @Nullable Object> extends StructuredConfigEntry<T> {
|
||||||
|
String NON_NULL_KEY = ":nonNull";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default NullableConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
|
||||||
|
StructuredConfigEntry.super.apply(function);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigEntry<T> nonNullEntry();
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
public class DefaultConfigContainer<T extends @Nullable Object> implements ConfigContainer<T> {
|
||||||
@Getter
|
@Getter
|
||||||
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
||||||
private final Set<Class<? extends TweedExtension>> requestedExtensions = new HashSet<>();
|
private final Set<Class<? extends TweedExtension>> requestedExtensions = new HashSet<>();
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package de.siphalor.tweed5.core.impl.entry;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class NullableConfigEntryImpl<T extends @Nullable Object> extends BaseConfigEntry<T> implements NullableConfigEntry<T> {
|
||||||
|
private final ConfigEntry<T> nonNullEntry;
|
||||||
|
|
||||||
|
public NullableConfigEntryImpl(
|
||||||
|
ConfigContainer<?> container,
|
||||||
|
Class<T> valueClass,
|
||||||
|
ConfigEntry<T> nonNullEntry
|
||||||
|
) {
|
||||||
|
super(container, valueClass);
|
||||||
|
this.nonNullEntry = nonNullEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ConfigEntry<?>> subEntries() {
|
||||||
|
return Collections.singletonMap(NON_NULL_KEY, nonNullEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
||||||
|
if (value != null) {
|
||||||
|
if (visitor.enterStructuredEntry(this, value)) {
|
||||||
|
if (visitor.enterStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY)) {
|
||||||
|
nonNullEntry.visitInOrder(visitor, value);
|
||||||
|
visitor.leaveStructuredSubEntry(NON_NULL_KEY, NON_NULL_KEY);
|
||||||
|
}
|
||||||
|
visitor.leaveStructuredEntry(this, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T deepCopy(T value) {
|
||||||
|
if (value != null) {
|
||||||
|
return nonNullEntry.deepCopy(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.comment.impl;
|
package de.siphalor.tweed5.defaultextensions.comment.impl;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||||
|
import de.siphalor.tweed5.core.impl.entry.NullableConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
@@ -36,7 +38,7 @@ class CommentExtensionImplTest {
|
|||||||
|
|
||||||
private DefaultConfigContainer<@NonNull Map<String, Object>> configContainer;
|
private DefaultConfigContainer<@NonNull Map<String, Object>> configContainer;
|
||||||
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||||
private SimpleConfigEntry<Integer> intEntry;
|
private NullableConfigEntry<Integer> intEntry;
|
||||||
private SimpleConfigEntry<String> stringEntry;
|
private SimpleConfigEntry<String> stringEntry;
|
||||||
private SimpleConfigEntry<Long> noCommentEntry;
|
private SimpleConfigEntry<Long> noCommentEntry;
|
||||||
|
|
||||||
@@ -49,9 +51,13 @@ class CommentExtensionImplTest {
|
|||||||
configContainer.registerExtensions(extraExtensions);
|
configContainer.registerExtensions(extraExtensions);
|
||||||
configContainer.finishExtensionSetup();
|
configContainer.finishExtensionSetup();
|
||||||
|
|
||||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
intEntry = new NullableConfigEntryImpl<>(
|
||||||
.apply(entryReaderWriter(intReaderWriter()))
|
configContainer, Integer.class,
|
||||||
.apply(baseComment("It is an integer"));
|
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
|
.apply(entryReaderWriter(intReaderWriter()))
|
||||||
|
.apply(baseComment("It is an integer")))
|
||||||
|
.apply(entryReaderWriter(nullableReaderWriter()))
|
||||||
|
.apply(baseComment("This is nullable"));
|
||||||
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
|
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
|
||||||
.apply(entryReaderWriter(stringReaderWriter()))
|
.apply(entryReaderWriter(stringReaderWriter()))
|
||||||
.apply(baseComment("It is a string"));
|
.apply(baseComment("It is a string"));
|
||||||
@@ -80,7 +86,7 @@ class CommentExtensionImplTest {
|
|||||||
configContainer.initialize();
|
configContainer.initialize();
|
||||||
|
|
||||||
CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow();
|
CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow();
|
||||||
assertEquals("It is an integer", commentExtension.getFullComment(intEntry));
|
assertEquals("This is nullable", commentExtension.getFullComment(intEntry));
|
||||||
assertEquals("It is a string", commentExtension.getFullComment(stringEntry));
|
assertEquals("It is a string", commentExtension.getFullComment(stringEntry));
|
||||||
assertNull(commentExtension.getFullComment(noCommentEntry));
|
assertNull(commentExtension.getFullComment(noCommentEntry));
|
||||||
}
|
}
|
||||||
@@ -91,7 +97,7 @@ class CommentExtensionImplTest {
|
|||||||
configContainer.initialize();
|
configContainer.initialize();
|
||||||
|
|
||||||
CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow();
|
CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow();
|
||||||
assertEquals("The comment is:\nIt is an integer\nEND", commentExtension.getFullComment(intEntry));
|
assertEquals("The comment is:\nThis is nullable\nEND", commentExtension.getFullComment(intEntry));
|
||||||
assertEquals("The comment is:\nIt is a string\nEND", commentExtension.getFullComment(stringEntry));
|
assertEquals("The comment is:\nIt is a string\nEND", commentExtension.getFullComment(stringEntry));
|
||||||
assertEquals("The comment is:\n\nEND", commentExtension.getFullComment(noCommentEntry));
|
assertEquals("The comment is:\n\nEND", commentExtension.getFullComment(noCommentEntry));
|
||||||
}
|
}
|
||||||
@@ -120,6 +126,7 @@ class CommentExtensionImplTest {
|
|||||||
// This is the root value.
|
// This is the root value.
|
||||||
// It is the topmost value in the tree.
|
// It is the topmost value in the tree.
|
||||||
{
|
{
|
||||||
|
\t// This is nullable
|
||||||
\t// It is an integer
|
\t// It is an integer
|
||||||
\tint: 123
|
\tint: 123
|
||||||
\t// It is a string
|
\t// It is a string
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.impl.DefaultConfigContainer;
|
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||||
import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
|
||||||
|
import de.siphalor.tweed5.core.impl.entry.NullableConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
@@ -51,40 +52,39 @@ class PatchExtensionImplTest {
|
|||||||
|
|
||||||
var int1Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
var int1Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
.apply(entryReaderWriter(intReaderWriter()));
|
.apply(entryReaderWriter(intReaderWriter()));
|
||||||
var int2Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
var int2Entry = new NullableConfigEntryImpl<>(
|
||||||
.apply(entryReaderWriter(
|
configContainer, Integer.class,
|
||||||
nullableReader(intReaderWriter()),
|
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
nullableWriter(intReaderWriter())
|
.apply(entryReaderWriter(intReaderWriter())));
|
||||||
));
|
var listEntry = new NullableConfigEntryImpl<>(
|
||||||
var listEntry = new CollectionConfigEntryImpl<>(
|
|
||||||
configContainer,
|
configContainer,
|
||||||
(Class<List<Integer>>)(Class) List.class,
|
(Class<List<Integer>>)(Class) List.class,
|
||||||
ArrayList::new,
|
new CollectionConfigEntryImpl<>(
|
||||||
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
configContainer,
|
||||||
.apply(entryReaderWriter(intReaderWriter()))
|
(Class<List<Integer>>)(Class) List.class,
|
||||||
)
|
ArrayList::new,
|
||||||
.apply(entryReaderWriter(
|
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
nullableReader(collectionReaderWriter()),
|
.apply(entryReaderWriter(intReaderWriter()))
|
||||||
nullableWriter(collectionReaderWriter())
|
).apply(entryReaderWriter(collectionReaderWriter()))
|
||||||
));
|
).apply(entryReaderWriter(nullableReaderWriter()));
|
||||||
|
|
||||||
var nestedInt1Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
var nestedInt1Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
.apply(entryReaderWriter(intReaderWriter()));
|
.apply(entryReaderWriter(intReaderWriter()));
|
||||||
var nestedInt2Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
var nestedInt2Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
.apply(entryReaderWriter(intReaderWriter()));
|
.apply(entryReaderWriter(intReaderWriter()));
|
||||||
var compoundEntry = new StaticMapCompoundConfigEntryImpl<>(
|
var compoundEntry = new NullableConfigEntryImpl<>(
|
||||||
configContainer,
|
configContainer,
|
||||||
(Class<Map<String, Object>>)(Class) Map.class,
|
(Class<Map<String, Object>>)(Class) Map.class,
|
||||||
HashMap::new,
|
new StaticMapCompoundConfigEntryImpl<>(
|
||||||
sequencedMap(List.of(
|
configContainer,
|
||||||
entry("int1", nestedInt1Entry),
|
(Class<Map<String, Object>>)(Class) Map.class,
|
||||||
entry("int2", nestedInt2Entry)
|
HashMap::new,
|
||||||
))
|
sequencedMap(List.of(
|
||||||
)
|
entry("int1", nestedInt1Entry),
|
||||||
.apply(entryReaderWriter(
|
entry("int2", nestedInt2Entry)
|
||||||
nullableReader(compoundReaderWriter()),
|
))
|
||||||
nullableWriter(compoundReaderWriter())
|
).apply(entryReaderWriter(compoundReaderWriter()))
|
||||||
));
|
).apply(entryReaderWriter(nullableReaderWriter()));
|
||||||
|
|
||||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||||
configContainer,
|
configContainer,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.pather.api;
|
package de.siphalor.tweed5.defaultextensions.pather.api;
|
||||||
|
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataToken;
|
import de.siphalor.tweed5.dataapi.api.TweedDataToken;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataTokens;
|
import de.siphalor.tweed5.dataapi.api.TweedDataTokens;
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package de.siphalor.tweed5.data.extension.api.readwrite;
|
|||||||
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
||||||
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.NullableConfigEntry;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.jspecify.annotations.NonNull;
|
import org.jspecify.annotations.NonNull;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@@ -51,12 +53,25 @@ public class TweedEntryReaderWriters {
|
|||||||
return (TweedEntryReaderWriter<T, @NonNull ConfigEntry<T>>)(TweedEntryReaderWriter) TweedEntryReaderWriterImpls.ENUM_READER_WRITER;
|
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) {
|
public static <T extends @Nullable Object> TweedEntryReaderWriter<T, NullableConfigEntry<T>> nullableReaderWriter() {
|
||||||
return new TweedEntryReaderWriterImpls.NullableReader<>(delegate);
|
//noinspection unchecked
|
||||||
|
return (TweedEntryReaderWriter<T, @NonNull NullableConfigEntry<T>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.NULLABLE_READER_WRITER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated You probably want to use {@link #nullableReaderWriter()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> nullableReader(TweedEntryReader<T, C> delegate) {
|
||||||
|
return new TweedEntryReaderWriterImpls.FixedNullableReader<>(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated You probably want to use {@link #nullableReaderWriter()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> nullableWriter(TweedEntryWriter<T, C> delegate) {
|
public static <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> nullableWriter(TweedEntryWriter<T, C> delegate) {
|
||||||
return new TweedEntryReaderWriterImpls.NullableWriter<>(delegate);
|
return new TweedEntryReaderWriterImpls.FixedNullableWriter<>(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, C extends Collection<T>> TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> collectionReaderWriter() {
|
public static <T, C extends Collection<T>> TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> collectionReaderWriter() {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import com.google.auto.service.AutoService;
|
|||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
|
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
|
||||||
|
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
|
|
||||||
@@ -39,22 +43,32 @@ public class DefaultTweedEntryReaderWriterImplsProvider implements TweedReaderWr
|
|||||||
context.registerWriterFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
|
context.registerWriterFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
|
||||||
context.registerReaderFactory("tweed5.enum", new StaticReaderWriterFactory<>(enumReaderWriter()));
|
context.registerReaderFactory("tweed5.enum", new StaticReaderWriterFactory<>(enumReaderWriter()));
|
||||||
context.registerWriterFactory("tweed5.enum", new StaticReaderWriterFactory<>(enumReaderWriter()));
|
context.registerWriterFactory("tweed5.enum", new StaticReaderWriterFactory<>(enumReaderWriter()));
|
||||||
|
context.registerReaderFactory("tweed5.nullable", new NullableReaderWriterFactory<>(
|
||||||
|
TweedEntryReaderWriters::nullableReader
|
||||||
|
));
|
||||||
|
context.registerWriterFactory("tweed5.nullable", new NullableReaderWriterFactory<>(
|
||||||
|
TweedEntryReaderWriters::nullableWriter
|
||||||
|
));
|
||||||
context.registerReaderFactory("tweed5.collection", new StaticReaderWriterFactory<>(collectionReaderWriter()));
|
context.registerReaderFactory("tweed5.collection", new StaticReaderWriterFactory<>(collectionReaderWriter()));
|
||||||
context.registerWriterFactory("tweed5.collection", new StaticReaderWriterFactory<>(collectionReaderWriter()));
|
context.registerWriterFactory("tweed5.collection", new StaticReaderWriterFactory<>(collectionReaderWriter()));
|
||||||
context.registerReaderFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
context.registerReaderFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
||||||
context.registerWriterFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
context.registerWriterFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
||||||
|
}
|
||||||
|
|
||||||
context.registerReaderFactory("tweed5.nullable", delegateReaders -> {
|
@RequiredArgsConstructor
|
||||||
if (delegateReaders.length != 1) {
|
private static class NullableReaderWriterFactory<T> implements ReaderWriterFactory<T> {
|
||||||
throw new IllegalArgumentException("Nullable reader requires a single delegate argument, got " + delegateReaders.length);
|
private final Function<T, T> delegateBasedFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T create(T... delegateReaderWriters) {
|
||||||
|
if (delegateReaderWriters.length == 0) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) TweedEntryReaderWriters.nullableReaderWriter();
|
||||||
|
} else if (delegateReaderWriters.length == 1) {
|
||||||
|
return delegateBasedFactory.apply(delegateReaderWriters[0]);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Nullable readers and writers may have only one or zero delegates");
|
||||||
}
|
}
|
||||||
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]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.siphalor.tweed5.data.extension.impl;
|
|||||||
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
||||||
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.NullableConfigEntry;
|
||||||
import de.siphalor.tweed5.data.extension.api.*;
|
import de.siphalor.tweed5.data.extension.api.*;
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
||||||
import de.siphalor.tweed5.dataapi.api.*;
|
import de.siphalor.tweed5.dataapi.api.*;
|
||||||
@@ -28,13 +29,48 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
public static final TweedEntryReaderWriter<String, ConfigEntry<String>> STRING_READER_WRITER = new PrimitiveReaderWriter<>(TweedDataToken::readAsString, TweedDataVisitor::visitString);
|
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<Enum<?>, ConfigEntry<Enum<?>>> ENUM_READER_WRITER = new EnumReaderWriter<>();
|
||||||
|
|
||||||
|
public static final TweedEntryReaderWriter<Object, NullableConfigEntry<Object>> NULLABLE_READER_WRITER = new NullableReaderWriter<>();
|
||||||
public static final TweedEntryReaderWriter<Collection<Object>, CollectionConfigEntry<Object, Collection<Object>>> COLLECTION_READER_WRITER = new CollectionReaderWriter<>();
|
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, CompoundConfigEntry<Object>> COMPOUND_READER_WRITER = new CompoundReaderWriter<>();
|
||||||
|
|
||||||
public static final TweedEntryReaderWriter<Object, ConfigEntry<Object>> NOOP_READER_WRITER = new NoopReaderWriter();
|
public static final TweedEntryReaderWriter<Object, ConfigEntry<Object>> NOOP_READER_WRITER = new NoopReaderWriter();
|
||||||
|
|
||||||
|
public static class NullableReaderWriter<T extends @Nullable Object> implements TweedEntryReaderWriter<T, NullableConfigEntry<T>> {
|
||||||
|
@Override
|
||||||
|
public T read(TweedDataReader reader, NullableConfigEntry<T> entry, TweedReadContext context)
|
||||||
|
throws TweedEntryReadException {
|
||||||
|
try {
|
||||||
|
if (reader.peekToken().isNull()) {
|
||||||
|
reader.readToken();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (TweedDataReadException e) {
|
||||||
|
throw new TweedEntryReadException(e, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
TweedEntryReader<T, ConfigEntry<T>> nonNullReader = context.readWriteExtension().getReaderChain(entry.nonNullEntry());
|
||||||
|
return nonNullReader.read(reader, entry.nonNullEntry(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(
|
||||||
|
TweedDataVisitor writer,
|
||||||
|
@Nullable T value,
|
||||||
|
NullableConfigEntry<T> entry,
|
||||||
|
TweedWriteContext context
|
||||||
|
) throws TweedEntryWriteException, TweedDataWriteException {
|
||||||
|
if (value == null) {
|
||||||
|
writer.visitNull();
|
||||||
|
} else {
|
||||||
|
TweedEntryWriter<T, ConfigEntry<T>> nonNullWriter = context.readWriteExtension().getWriterChain(entry.nonNullEntry());
|
||||||
|
nonNullWriter.write(writer, value, entry.nonNullEntry(), context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public static class NullableReader<T extends @Nullable Object, C extends ConfigEntry<T>> implements TweedEntryReader<T, C> {
|
public static class FixedNullableReader<T extends @Nullable Object, C extends ConfigEntry<T>> implements TweedEntryReader<T, C> {
|
||||||
private final TweedEntryReader<T, C> delegate;
|
private final TweedEntryReader<T, C> delegate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,8 +87,9 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public static class NullableWriter<T extends @Nullable Object, C extends ConfigEntry<T>> implements TweedEntryWriter<T, C> {
|
public static class FixedNullableWriter<T extends @Nullable Object, C extends ConfigEntry<T>> implements TweedEntryWriter<T, C> {
|
||||||
private final TweedEntryWriter<T, C> delegate;
|
private final TweedEntryWriter<T, C> delegate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package de.siphalor.tweed5.weaver.pojoext.serde.api.auto;
|
|||||||
|
|
||||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
|
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
|
||||||
|
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
|
||||||
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
@@ -113,6 +115,15 @@ public class AutoReadWritePojoWeavingProcessor implements TweedPojoWeavingExtens
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void afterWeaveEntry(ActualType<T> valueType, ConfigEntry<T> configEntry, WeavingContext context) {
|
public <T> void afterWeaveEntry(ActualType<T> valueType, ConfigEntry<T> configEntry, WeavingContext context) {
|
||||||
|
if (configEntry instanceof NullableConfigEntry) {
|
||||||
|
readWriteExtension.setEntryReaderWriter(
|
||||||
|
configEntry,
|
||||||
|
TweedEntryReaderWriters.nullableReaderWriter(),
|
||||||
|
TweedEntryReaderWriters.nullableReaderWriter()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert customDataAccess != null;
|
assert customDataAccess != null;
|
||||||
CustomData customData = context.extensionsData().get(customDataAccess);
|
CustomData customData = context.extensionsData().get(customDataAccess);
|
||||||
if (customData == null || customData.mappings().isEmpty()) {
|
if (customData == null || customData.mappings().isEmpty()) {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see AutoNullableReadWritePojoWeavingProcessor
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
public @interface AutoNullableReadWriteBehavior {
|
public @interface AutoNullableReadWriteBehavior {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import org.jspecify.annotations.Nullable;
|
|||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated The recommended approach is to use {@link de.siphalor.tweed5.weaver.pojo.api.weaving.NullablePojoWeaver}
|
||||||
|
* and set @{@link Nullable} on all nullable entries.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public class AutoNullableReadWritePojoWeavingProcessor implements TweedPojoWeavingExtension {
|
public class AutoNullableReadWritePojoWeavingProcessor implements TweedPojoWeavingExtension {
|
||||||
private final ReadWriteExtension readWriteExtension;
|
private final ReadWriteExtension readWriteExtension;
|
||||||
private @Nullable PatchworkPartAccess<CustomData> customDataAccess;
|
private @Nullable PatchworkPartAccess<CustomData> customDataAccess;
|
||||||
@@ -71,14 +76,14 @@ public class AutoNullableReadWritePojoWeavingProcessor implements TweedPojoWeavi
|
|||||||
if (definedEntryReader != null) {
|
if (definedEntryReader != null) {
|
||||||
readWriteExtension.setEntryReader(
|
readWriteExtension.setEntryReader(
|
||||||
configEntry,
|
configEntry,
|
||||||
new TweedEntryReaderWriterImpls.NullableReader<>(definedEntryReader)
|
new TweedEntryReaderWriterImpls.FixedNullableReader<>(definedEntryReader)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
val definedEntryWriter = readWriteExtension.getDefinedEntryWriter(configEntry);
|
val definedEntryWriter = readWriteExtension.getDefinedEntryWriter(configEntry);
|
||||||
if (definedEntryWriter != null) {
|
if (definedEntryWriter != null) {
|
||||||
readWriteExtension.setEntryWriter(
|
readWriteExtension.setEntryWriter(
|
||||||
configEntry,
|
configEntry,
|
||||||
new TweedEntryReaderWriterImpls.NullableWriter<>(definedEntryWriter)
|
new TweedEntryReaderWriterImpls.FixedNullableWriter<>(definedEntryWriter)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojoext.serde.api.nullable;
|
package de.siphalor.tweed5.weaver.pojoext.serde.api.nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see AutoNullableReadWriteBehavior
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public enum AutoReadWriteNullability {
|
public enum AutoReadWriteNullability {
|
||||||
NON_NULL,
|
NON_NULL,
|
||||||
NULLABLE,
|
NULLABLE,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
|||||||
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
||||||
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.NullableConfigEntry;
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
||||||
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
||||||
@@ -12,9 +13,11 @@ import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
|||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.DefaultWeavingExtensions;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.DefaultWeavingExtensions;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.NullablePojoWeaver;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.jspecify.annotations.NullUnmarked;
|
import org.jspecify.annotations.NullUnmarked;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.write;
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.write;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
@NullUnmarked
|
@NullUnmarked
|
||||||
class AutoReadWritePojoWeavingProcessorTest {
|
class AutoReadWritePojoWeavingProcessorTest {
|
||||||
@@ -50,7 +54,11 @@ class AutoReadWritePojoWeavingProcessorTest {
|
|||||||
assertReaderAndWriter(primitiveIntEntry, TweedEntryReaderWriterImpls.INT_READER_WRITER);
|
assertReaderAndWriter(primitiveIntEntry, TweedEntryReaderWriterImpls.INT_READER_WRITER);
|
||||||
|
|
||||||
var boxedLongEntry = rootEntry.subEntries().get("boxedLong");
|
var boxedLongEntry = rootEntry.subEntries().get("boxedLong");
|
||||||
assertReaderAndWriter(boxedLongEntry, TweedEntryReaderWriterImpls.LONG_READER_WRITER);
|
assertReaderAndWriter(boxedLongEntry, TweedEntryReaderWriterImpls.NULLABLE_READER_WRITER);
|
||||||
|
assertThat(boxedLongEntry).asInstanceOf(type(NullableConfigEntry.class))
|
||||||
|
.satisfies(nullableEntry -> assertReaderAndWriter(
|
||||||
|
nullableEntry.nonNullEntry(), TweedEntryReaderWriterImpls.LONG_READER_WRITER
|
||||||
|
));
|
||||||
|
|
||||||
var stringEntry = rootEntry.subEntries().get("string");
|
var stringEntry = rootEntry.subEntries().get("string");
|
||||||
assertReaderAndWriter(stringEntry, TweedEntryReaderWriterImpls.STRING_READER_WRITER);
|
assertReaderAndWriter(stringEntry, TweedEntryReaderWriterImpls.STRING_READER_WRITER);
|
||||||
@@ -115,6 +123,7 @@ class AutoReadWritePojoWeavingProcessorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PojoWeaving(extensions = ReadWriteExtension.class)
|
@PojoWeaving(extensions = ReadWriteExtension.class)
|
||||||
|
@PojoWeavingExtension(NullablePojoWeaver.class)
|
||||||
@DefaultWeavingExtensions
|
@DefaultWeavingExtensions
|
||||||
@PojoWeavingExtension(AutoReadWritePojoWeavingProcessor.class)
|
@PojoWeavingExtension(AutoReadWritePojoWeavingProcessor.class)
|
||||||
@DefaultReadWriteMappings
|
@DefaultReadWriteMappings
|
||||||
@@ -122,7 +131,7 @@ class AutoReadWritePojoWeavingProcessorTest {
|
|||||||
@Data
|
@Data
|
||||||
public static class AnnotatedConfig {
|
public static class AnnotatedConfig {
|
||||||
private int primitiveInt;
|
private int primitiveInt;
|
||||||
private Long boxedLong;
|
private @Nullable Long boxedLong;
|
||||||
private String string;
|
private String string;
|
||||||
private List<@CompoundWeaving Nested> nesteds;
|
private List<@CompoundWeaving Nested> nesteds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,22 +85,22 @@ class AutoNullableReadWritePojoWeavingProcessorTest {
|
|||||||
|
|
||||||
private void assertNullableReader(ConfigEntry<?> entry) {
|
private void assertNullableReader(ConfigEntry<?> entry) {
|
||||||
assertThat(readWriteExtension.getDefinedEntryReader(entry))
|
assertThat(readWriteExtension.getDefinedEntryReader(entry))
|
||||||
.isInstanceOf(TweedEntryReaderWriterImpls.NullableReader.class);
|
.isInstanceOf(TweedEntryReaderWriterImpls.FixedNullableReader.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNonNullableReader(ConfigEntry<?> entry) {
|
private void assertNonNullableReader(ConfigEntry<?> entry) {
|
||||||
assertThat(readWriteExtension.getDefinedEntryReader(entry))
|
assertThat(readWriteExtension.getDefinedEntryReader(entry))
|
||||||
.isNotInstanceOf(TweedEntryReaderWriterImpls.NullableReader.class);
|
.isNotInstanceOf(TweedEntryReaderWriterImpls.FixedNullableReader.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNullableWriter(ConfigEntry<?> entry) {
|
private void assertNullableWriter(ConfigEntry<?> entry) {
|
||||||
assertThat(readWriteExtension.getDefinedEntryWriter(entry))
|
assertThat(readWriteExtension.getDefinedEntryWriter(entry))
|
||||||
.isInstanceOf(TweedEntryReaderWriterImpls.NullableWriter.class);
|
.isInstanceOf(TweedEntryReaderWriterImpls.FixedNullableWriter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNonNullableWriter(ConfigEntry<?> entry) {
|
private void assertNonNullableWriter(ConfigEntry<?> entry) {
|
||||||
assertThat(readWriteExtension.getDefinedEntryWriter(entry))
|
assertThat(readWriteExtension.getDefinedEntryWriter(entry))
|
||||||
.isNotInstanceOf(TweedEntryReaderWriterImpls.NullableWriter.class);
|
.isNotInstanceOf(TweedEntryReaderWriterImpls.FixedNullableWriter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.impl.entry.NullableConfigEntryImpl;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class NullablePojoWeaver implements TweedPojoWeavingExtension {
|
||||||
|
private @Nullable PatchworkPartAccess<CustomData> customDataAccess;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(SetupContext context) {
|
||||||
|
customDataAccess = context.registerWeavingContextExtensionData(CustomData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void beforeWeaveEntry(ActualType<T> valueType, Patchwork extensionsData, ProtoWeavingContext context) {
|
||||||
|
assert customDataAccess != null;
|
||||||
|
|
||||||
|
Boolean nullable = null;
|
||||||
|
for (Annotation annotation : valueType.getAnnotations()) {
|
||||||
|
String annotationName = annotation.annotationType().getSimpleName().toLowerCase(Locale.ROOT);
|
||||||
|
switch (annotationName) {
|
||||||
|
case "nullable":
|
||||||
|
nullable = true;
|
||||||
|
break;
|
||||||
|
case "nonnull":
|
||||||
|
case "notnull":
|
||||||
|
nullable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionsData.set(customDataAccess, new CustomData(Boolean.TRUE.equals(nullable)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
|
assert customDataAccess != null;
|
||||||
|
|
||||||
|
CustomData customData = context.extensionsData().get(customDataAccess);
|
||||||
|
if (customData == null || !customData.nullable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
customData.nullable(false);
|
||||||
|
return new NullableConfigEntryImpl<>(
|
||||||
|
context.configContainer(),
|
||||||
|
valueType.declaredType(),
|
||||||
|
context.weavePseudoEntry(context, "nonNull", context.extensionsData())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Setter
|
||||||
|
private static class CustomData {
|
||||||
|
private boolean nullable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ public class WeavingContext {
|
|||||||
WeavingFn weavingFunction;
|
WeavingFn weavingFunction;
|
||||||
ConfigContainer<?> configContainer;
|
ConfigContainer<?> configContainer;
|
||||||
String[] path;
|
String[] path;
|
||||||
|
ActualType<?> valueType;
|
||||||
|
boolean isPseudo;
|
||||||
Patchwork extensionsData;
|
Patchwork extensionsData;
|
||||||
AnnotatedElement annotations;
|
AnnotatedElement annotations;
|
||||||
|
|
||||||
@@ -27,7 +29,12 @@ public class WeavingContext {
|
|||||||
return weavingFunction.weaveEntry(valueType, extensionsData, context);
|
return weavingFunction.weaveEntry(valueType, extensionsData, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> ConfigEntry<T> weavePseudoEntry(WeavingContext parentContext, String pseudoEntryName, Patchwork extensionsData) {
|
||||||
|
return weavingFunction.weavePseudoEntry(parentContext, pseudoEntryName, extensionsData);
|
||||||
|
}
|
||||||
|
|
||||||
public interface WeavingFn {
|
public interface WeavingFn {
|
||||||
<T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, Patchwork extensionsData, ProtoWeavingContext context);
|
<T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, Patchwork extensionsData, ProtoWeavingContext context);
|
||||||
|
<T> ConfigEntry<T> weavePseudoEntry(WeavingContext parentContext, String pseudoEntryName, Patchwork extensionsData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) {
|
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
||||||
if (visitor.enterStructuredEntry(this, value)) {
|
if (visitor.enterStructuredEntry(this, value)) {
|
||||||
subEntries.forEach((key, entry) -> {
|
subEntries.forEach((key, entry) -> {
|
||||||
if (visitor.enterStructuredSubEntry(key, key)) {
|
if (visitor.enterStructuredSubEntry(key, key)) {
|
||||||
|
|||||||
@@ -33,6 +33,21 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
private final AnnotatedElement pojoAnnotations;
|
private final AnnotatedElement pojoAnnotations;
|
||||||
private final ConfigContainer<T> configContainer;
|
private final ConfigContainer<T> configContainer;
|
||||||
private final Collection<TweedPojoWeavingExtension> weavingExtensions;
|
private final Collection<TweedPojoWeavingExtension> weavingExtensions;
|
||||||
|
private final WeavingContext.WeavingFn weavingContextFn = new WeavingContext.WeavingFn() {
|
||||||
|
@Override
|
||||||
|
public <U> ConfigEntry<U> weaveEntry(
|
||||||
|
ActualType<U> valueType,
|
||||||
|
Patchwork extensionsData,
|
||||||
|
ProtoWeavingContext context
|
||||||
|
) {
|
||||||
|
return TweedPojoWeaverBootstrapper.this.weaveEntry(valueType, extensionsData, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> ConfigEntry<U> weavePseudoEntry(WeavingContext parentContext, String pseudoEntryName, Patchwork extensionsData) {
|
||||||
|
return TweedPojoWeaverBootstrapper.this.weavePseudoEntry(parentContext, pseudoEntryName, extensionsData);
|
||||||
|
}
|
||||||
|
};
|
||||||
@Nullable
|
@Nullable
|
||||||
private PatchworkFactory weavingExtensionsPatchworkFactory;
|
private PatchworkFactory weavingExtensionsPatchworkFactory;
|
||||||
|
|
||||||
@@ -119,18 +134,19 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <U> ConfigEntry<U> weaveEntry(
|
private <U> ConfigEntry<U> weaveEntry(
|
||||||
ActualType<U> dataClass,
|
ActualType<U> valueType,
|
||||||
Patchwork extensionsData,
|
Patchwork extensionsData,
|
||||||
ProtoWeavingContext protoContext
|
ProtoWeavingContext protoContext
|
||||||
) {
|
) {
|
||||||
extensionsData = extensionsData.copy();
|
extensionsData = extensionsData.copy();
|
||||||
|
|
||||||
runBeforeWeaveHooks(dataClass, extensionsData, protoContext);
|
runBeforeWeaveHooks(valueType, extensionsData, protoContext);
|
||||||
|
|
||||||
WeavingContext context = WeavingContext.builder()
|
WeavingContext context = WeavingContext.builder()
|
||||||
.parent(protoContext.parent())
|
.parent(protoContext.parent())
|
||||||
.weavingFunction(this::weaveEntry)
|
.weavingFunction(weavingContextFn)
|
||||||
.configContainer(configContainer)
|
.configContainer(configContainer)
|
||||||
|
.valueType(valueType)
|
||||||
.path(protoContext.path())
|
.path(protoContext.path())
|
||||||
.extensionsData(extensionsData)
|
.extensionsData(extensionsData)
|
||||||
.annotations(new AnnotationInheritanceAwareAnnotatedElement(protoContext.annotations()))
|
.annotations(new AnnotationInheritanceAwareAnnotatedElement(protoContext.annotations()))
|
||||||
@@ -138,17 +154,70 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
|
|
||||||
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
|
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
|
||||||
try {
|
try {
|
||||||
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(dataClass, context);
|
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(valueType, context);
|
||||||
if (configEntry != null) {
|
if (configEntry != null) {
|
||||||
runAfterWeaveHooks(dataClass, configEntry, context);
|
runAfterWeaveHooks(valueType, configEntry, context);
|
||||||
return configEntry;
|
return configEntry;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to run Tweed POJO weaver (" + weavingExtension.getClass().getName() + ")", e);
|
log.error(
|
||||||
|
"Failed to run Tweed POJO weaver (" + weavingExtension.getClass().getName() + ")"
|
||||||
|
+ " for entry at " + Arrays.toString(context.path()),
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new PojoWeavingException("Failed to weave " + dataClass + ": No matching weavers found");
|
throw new PojoWeavingException(
|
||||||
|
"Failed to weave entry for " + valueType + " at " + Arrays.toString(context.path())
|
||||||
|
+ ": No matching weavers found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <U> ConfigEntry<U> weavePseudoEntry(
|
||||||
|
WeavingContext parentContext,
|
||||||
|
String pseudoEntryName,
|
||||||
|
Patchwork extensionsData
|
||||||
|
) {
|
||||||
|
extensionsData = extensionsData.copy();
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
ActualType<U> valueType = (ActualType<U>) parentContext.valueType();
|
||||||
|
|
||||||
|
String[] path = Arrays.copyOf(parentContext.path(), parentContext.path().length + 1);
|
||||||
|
path[path.length - 1] = ":" + pseudoEntryName;
|
||||||
|
|
||||||
|
WeavingContext context = WeavingContext.builder()
|
||||||
|
.parent(parentContext)
|
||||||
|
.weavingFunction(weavingContextFn)
|
||||||
|
.configContainer(configContainer)
|
||||||
|
.path(path)
|
||||||
|
.valueType(parentContext.valueType())
|
||||||
|
.isPseudo(true)
|
||||||
|
.extensionsData(extensionsData)
|
||||||
|
.annotations(parentContext.annotations())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
|
||||||
|
try {
|
||||||
|
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(valueType, context);
|
||||||
|
if (configEntry != null) {
|
||||||
|
runAfterWeaveHooks(valueType, configEntry, context);
|
||||||
|
return configEntry;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(
|
||||||
|
"Failed to run Tweed POJO weaver (" + weavingExtension.getClass().getName() + ")"
|
||||||
|
+ " for pseudo entry at " + Arrays.toString(context.path()),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PojoWeavingException(
|
||||||
|
"Failed to weave pseudo entry for " + valueType + " at " + Arrays.toString(context.path())
|
||||||
|
+ ": No matching weavers found"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <U> void runBeforeWeaveHooks(
|
private <U> void runBeforeWeaveHooks(
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import de.siphalor.tweed5.patchwork.api.Patchwork;
|
|||||||
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||||
import org.jspecify.annotations.NullUnmarked;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
||||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||||
@@ -18,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@NullUnmarked
|
|
||||||
class CompoundPojoWeaverTest {
|
class CompoundPojoWeaverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -30,27 +30,40 @@ class CompoundPojoWeaverTest {
|
|||||||
|
|
||||||
PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build();
|
PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build();
|
||||||
|
|
||||||
|
AtomicReference<WeavingContext.@Nullable WeavingFn> weavingFunction = new AtomicReference<>();
|
||||||
|
weavingFunction.set(new WeavingContext.WeavingFn() {
|
||||||
|
@Override
|
||||||
|
public <T> ConfigEntry<T> weaveEntry(
|
||||||
|
ActualType<T> valueType,
|
||||||
|
Patchwork extensionsData,
|
||||||
|
ProtoWeavingContext protoContext
|
||||||
|
) {
|
||||||
|
WeavingContext context = WeavingContext.builder()
|
||||||
|
.configContainer(protoContext.configContainer())
|
||||||
|
.annotations(valueType)
|
||||||
|
.extensionsData(extensionsData.copy())
|
||||||
|
.path(protoContext.path())
|
||||||
|
.weavingFunction(Objects.requireNonNull(weavingFunction.get()))
|
||||||
|
.build();
|
||||||
|
return Objects.requireNonNullElseGet(
|
||||||
|
compoundWeaver.weaveEntry(valueType, context),
|
||||||
|
() -> new SimpleConfigEntryImpl<>(context.configContainer(), valueType.declaredType())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ConfigEntry<T> weavePseudoEntry(
|
||||||
|
WeavingContext parentContext,
|
||||||
|
String pseudoEntryName,
|
||||||
|
Patchwork extensionsData
|
||||||
|
) {
|
||||||
|
assert false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
WeavingContext weavingContext = WeavingContext.builder()
|
WeavingContext weavingContext = WeavingContext.builder()
|
||||||
.weavingFunction(new WeavingContext.WeavingFn() {
|
.weavingFunction(Objects.requireNonNull(weavingFunction.get()))
|
||||||
@Override
|
|
||||||
public <T> ConfigEntry<T> weaveEntry(
|
|
||||||
ActualType<T> valueType,
|
|
||||||
Patchwork extensionsData,
|
|
||||||
ProtoWeavingContext protoContext
|
|
||||||
) {
|
|
||||||
WeavingContext context = WeavingContext.builder()
|
|
||||||
.configContainer(protoContext.configContainer())
|
|
||||||
.annotations(valueType)
|
|
||||||
.extensionsData(extensionsData.copy())
|
|
||||||
.path(protoContext.path())
|
|
||||||
.weavingFunction(protoContext.parent()::weaveEntry)
|
|
||||||
.build();
|
|
||||||
return Objects.requireNonNullElseGet(
|
|
||||||
compoundWeaver.weaveEntry(valueType, context),
|
|
||||||
() -> new SimpleConfigEntryImpl<>(context.configContainer(), valueType.declaredType())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.extensionsData(weavingContextExtensionDataFactory.create())
|
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||||
.annotations(Compound.class)
|
.annotations(Compound.class)
|
||||||
.path(new String[0])
|
.path(new String[0])
|
||||||
|
|||||||
Reference in New Issue
Block a user