[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.patchwork.api.Patchwork;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -26,7 +25,7 @@ public interface ConfigEntry<T extends @Nullable Object> {
|
||||
}
|
||||
|
||||
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.util.*;
|
||||
|
||||
public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
public class DefaultConfigContainer<T extends @Nullable Object> implements ConfigContainer<T> {
|
||||
@Getter
|
||||
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
||||
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;
|
||||
|
||||
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.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||
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.StaticMapCompoundConfigEntryImpl;
|
||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||
@@ -36,7 +38,7 @@ class CommentExtensionImplTest {
|
||||
|
||||
private DefaultConfigContainer<@NonNull Map<String, Object>> configContainer;
|
||||
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntry<Integer> intEntry;
|
||||
private NullableConfigEntry<Integer> intEntry;
|
||||
private SimpleConfigEntry<String> stringEntry;
|
||||
private SimpleConfigEntry<Long> noCommentEntry;
|
||||
|
||||
@@ -49,9 +51,13 @@ class CommentExtensionImplTest {
|
||||
configContainer.registerExtensions(extraExtensions);
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
intEntry = new NullableConfigEntryImpl<>(
|
||||
configContainer, Integer.class,
|
||||
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()))
|
||||
.apply(baseComment("It is an integer"));
|
||||
.apply(baseComment("It is an integer")))
|
||||
.apply(entryReaderWriter(nullableReaderWriter()))
|
||||
.apply(baseComment("This is nullable"));
|
||||
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
|
||||
.apply(entryReaderWriter(stringReaderWriter()))
|
||||
.apply(baseComment("It is a string"));
|
||||
@@ -80,7 +86,7 @@ class CommentExtensionImplTest {
|
||||
configContainer.initialize();
|
||||
|
||||
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));
|
||||
assertNull(commentExtension.getFullComment(noCommentEntry));
|
||||
}
|
||||
@@ -91,7 +97,7 @@ class CommentExtensionImplTest {
|
||||
configContainer.initialize();
|
||||
|
||||
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:\n\nEND", commentExtension.getFullComment(noCommentEntry));
|
||||
}
|
||||
@@ -120,6 +126,7 @@ class CommentExtensionImplTest {
|
||||
// This is the root value.
|
||||
// It is the topmost value in the tree.
|
||||
{
|
||||
\t// This is nullable
|
||||
\t// It is an integer
|
||||
\tint: 123
|
||||
\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.impl.DefaultConfigContainer;
|
||||
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.StaticMapCompoundConfigEntryImpl;
|
||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||
@@ -51,28 +52,30 @@ class PatchExtensionImplTest {
|
||||
|
||||
var int1Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()));
|
||||
var int2Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(
|
||||
nullableReader(intReaderWriter()),
|
||||
nullableWriter(intReaderWriter())
|
||||
));
|
||||
var listEntry = new CollectionConfigEntryImpl<>(
|
||||
var int2Entry = new NullableConfigEntryImpl<>(
|
||||
configContainer, Integer.class,
|
||||
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter())));
|
||||
var listEntry = new NullableConfigEntryImpl<>(
|
||||
configContainer,
|
||||
(Class<List<Integer>>)(Class) List.class,
|
||||
new CollectionConfigEntryImpl<>(
|
||||
configContainer,
|
||||
(Class<List<Integer>>)(Class) List.class,
|
||||
ArrayList::new,
|
||||
new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()))
|
||||
)
|
||||
.apply(entryReaderWriter(
|
||||
nullableReader(collectionReaderWriter()),
|
||||
nullableWriter(collectionReaderWriter())
|
||||
));
|
||||
).apply(entryReaderWriter(collectionReaderWriter()))
|
||||
).apply(entryReaderWriter(nullableReaderWriter()));
|
||||
|
||||
var nestedInt1Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()));
|
||||
var nestedInt2Entry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()));
|
||||
var compoundEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||
var compoundEntry = new NullableConfigEntryImpl<>(
|
||||
configContainer,
|
||||
(Class<Map<String, Object>>)(Class) Map.class,
|
||||
new StaticMapCompoundConfigEntryImpl<>(
|
||||
configContainer,
|
||||
(Class<Map<String, Object>>)(Class) Map.class,
|
||||
HashMap::new,
|
||||
@@ -80,11 +83,8 @@ class PatchExtensionImplTest {
|
||||
entry("int1", nestedInt1Entry),
|
||||
entry("int2", nestedInt2Entry)
|
||||
))
|
||||
)
|
||||
.apply(entryReaderWriter(
|
||||
nullableReader(compoundReaderWriter()),
|
||||
nullableWriter(compoundReaderWriter())
|
||||
));
|
||||
).apply(entryReaderWriter(compoundReaderWriter()))
|
||||
).apply(entryReaderWriter(nullableReaderWriter()));
|
||||
|
||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||
configContainer,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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.TweedDataToken;
|
||||
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.CompoundConfigEntry;
|
||||
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.TweedEntryWriter;
|
||||
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -51,12 +53,25 @@ public class TweedEntryReaderWriters {
|
||||
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 extends @Nullable Object> TweedEntryReaderWriter<T, NullableConfigEntry<T>> nullableReaderWriter() {
|
||||
//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) {
|
||||
return new TweedEntryReaderWriterImpls.NullableWriter<>(delegate);
|
||||
return new TweedEntryReaderWriterImpls.FixedNullableWriter<>(delegate);
|
||||
}
|
||||
|
||||
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.TweedEntryWriter;
|
||||
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.*;
|
||||
|
||||
@@ -39,22 +43,32 @@ public class DefaultTweedEntryReaderWriterImplsProvider implements TweedReaderWr
|
||||
context.registerWriterFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
|
||||
context.registerReaderFactory("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.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]);
|
||||
});
|
||||
@RequiredArgsConstructor
|
||||
private static class NullableReaderWriterFactory<T> implements ReaderWriterFactory<T> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.CompoundConfigEntry;
|
||||
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.readwrite.TweedEntryReaderWriter;
|
||||
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<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<Object, CompoundConfigEntry<Object>> COMPOUND_READER_WRITER = new CompoundReaderWriter<>();
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
@Override
|
||||
@@ -51,8 +87,9 @@ public class TweedEntryReaderWriterImpls {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@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;
|
||||
|
||||
@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.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.TweedEntryReader;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||
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.patchwork.api.Patchwork;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||
@@ -113,6 +115,15 @@ public class AutoReadWritePojoWeavingProcessor implements TweedPojoWeavingExtens
|
||||
|
||||
@Override
|
||||
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;
|
||||
CustomData customData = context.extensionsData().get(customDataAccess);
|
||||
if (customData == null || customData.mappings().isEmpty()) {
|
||||
|
||||
@@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @see AutoNullableReadWritePojoWeavingProcessor
|
||||
*/
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface AutoNullableReadWriteBehavior {
|
||||
|
||||
@@ -17,6 +17,11 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
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 {
|
||||
private final ReadWriteExtension readWriteExtension;
|
||||
private @Nullable PatchworkPartAccess<CustomData> customDataAccess;
|
||||
@@ -71,14 +76,14 @@ public class AutoNullableReadWritePojoWeavingProcessor implements TweedPojoWeavi
|
||||
if (definedEntryReader != null) {
|
||||
readWriteExtension.setEntryReader(
|
||||
configEntry,
|
||||
new TweedEntryReaderWriterImpls.NullableReader<>(definedEntryReader)
|
||||
new TweedEntryReaderWriterImpls.FixedNullableReader<>(definedEntryReader)
|
||||
);
|
||||
}
|
||||
val definedEntryWriter = readWriteExtension.getDefinedEntryWriter(configEntry);
|
||||
if (definedEntryWriter != null) {
|
||||
readWriteExtension.setEntryWriter(
|
||||
configEntry,
|
||||
new TweedEntryReaderWriterImpls.NullableWriter<>(definedEntryWriter)
|
||||
new TweedEntryReaderWriterImpls.FixedNullableWriter<>(definedEntryWriter)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.serde.api.nullable;
|
||||
|
||||
/**
|
||||
* @see AutoNullableReadWriteBehavior
|
||||
*/
|
||||
@Deprecated
|
||||
public enum AutoReadWriteNullability {
|
||||
NON_NULL,
|
||||
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.CompoundConfigEntry;
|
||||
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.readwrite.TweedEntryReaderWriter;
|
||||
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.PojoWeaving;
|
||||
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 lombok.Data;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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 org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
|
||||
@NullUnmarked
|
||||
class AutoReadWritePojoWeavingProcessorTest {
|
||||
@@ -50,7 +54,11 @@ class AutoReadWritePojoWeavingProcessorTest {
|
||||
assertReaderAndWriter(primitiveIntEntry, TweedEntryReaderWriterImpls.INT_READER_WRITER);
|
||||
|
||||
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");
|
||||
assertReaderAndWriter(stringEntry, TweedEntryReaderWriterImpls.STRING_READER_WRITER);
|
||||
@@ -115,6 +123,7 @@ class AutoReadWritePojoWeavingProcessorTest {
|
||||
}
|
||||
|
||||
@PojoWeaving(extensions = ReadWriteExtension.class)
|
||||
@PojoWeavingExtension(NullablePojoWeaver.class)
|
||||
@DefaultWeavingExtensions
|
||||
@PojoWeavingExtension(AutoReadWritePojoWeavingProcessor.class)
|
||||
@DefaultReadWriteMappings
|
||||
@@ -122,7 +131,7 @@ class AutoReadWritePojoWeavingProcessorTest {
|
||||
@Data
|
||||
public static class AnnotatedConfig {
|
||||
private int primitiveInt;
|
||||
private Long boxedLong;
|
||||
private @Nullable Long boxedLong;
|
||||
private String string;
|
||||
private List<@CompoundWeaving Nested> nesteds;
|
||||
}
|
||||
|
||||
@@ -85,22 +85,22 @@ class AutoNullableReadWritePojoWeavingProcessorTest {
|
||||
|
||||
private void assertNullableReader(ConfigEntry<?> entry) {
|
||||
assertThat(readWriteExtension.getDefinedEntryReader(entry))
|
||||
.isInstanceOf(TweedEntryReaderWriterImpls.NullableReader.class);
|
||||
.isInstanceOf(TweedEntryReaderWriterImpls.FixedNullableReader.class);
|
||||
}
|
||||
|
||||
private void assertNonNullableReader(ConfigEntry<?> entry) {
|
||||
assertThat(readWriteExtension.getDefinedEntryReader(entry))
|
||||
.isNotInstanceOf(TweedEntryReaderWriterImpls.NullableReader.class);
|
||||
.isNotInstanceOf(TweedEntryReaderWriterImpls.FixedNullableReader.class);
|
||||
}
|
||||
|
||||
private void assertNullableWriter(ConfigEntry<?> entry) {
|
||||
assertThat(readWriteExtension.getDefinedEntryWriter(entry))
|
||||
.isInstanceOf(TweedEntryReaderWriterImpls.NullableWriter.class);
|
||||
.isInstanceOf(TweedEntryReaderWriterImpls.FixedNullableWriter.class);
|
||||
}
|
||||
|
||||
private void assertNonNullableWriter(ConfigEntry<?> entry) {
|
||||
assertThat(readWriteExtension.getDefinedEntryWriter(entry))
|
||||
.isNotInstanceOf(TweedEntryReaderWriterImpls.NullableWriter.class);
|
||||
.isNotInstanceOf(TweedEntryReaderWriterImpls.FixedNullableWriter.class);
|
||||
}
|
||||
|
||||
@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;
|
||||
ConfigContainer<?> configContainer;
|
||||
String[] path;
|
||||
ActualType<?> valueType;
|
||||
boolean isPseudo;
|
||||
Patchwork extensionsData;
|
||||
AnnotatedElement annotations;
|
||||
|
||||
@@ -27,7 +29,12 @@ public class WeavingContext {
|
||||
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 {
|
||||
<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
|
||||
public void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) {
|
||||
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
||||
if (visitor.enterStructuredEntry(this, value)) {
|
||||
subEntries.forEach((key, entry) -> {
|
||||
if (visitor.enterStructuredSubEntry(key, key)) {
|
||||
|
||||
@@ -33,6 +33,21 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
private final AnnotatedElement pojoAnnotations;
|
||||
private final ConfigContainer<T> configContainer;
|
||||
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
|
||||
private PatchworkFactory weavingExtensionsPatchworkFactory;
|
||||
|
||||
@@ -119,18 +134,19 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
}
|
||||
|
||||
private <U> ConfigEntry<U> weaveEntry(
|
||||
ActualType<U> dataClass,
|
||||
ActualType<U> valueType,
|
||||
Patchwork extensionsData,
|
||||
ProtoWeavingContext protoContext
|
||||
) {
|
||||
extensionsData = extensionsData.copy();
|
||||
|
||||
runBeforeWeaveHooks(dataClass, extensionsData, protoContext);
|
||||
runBeforeWeaveHooks(valueType, extensionsData, protoContext);
|
||||
|
||||
WeavingContext context = WeavingContext.builder()
|
||||
.parent(protoContext.parent())
|
||||
.weavingFunction(this::weaveEntry)
|
||||
.weavingFunction(weavingContextFn)
|
||||
.configContainer(configContainer)
|
||||
.valueType(valueType)
|
||||
.path(protoContext.path())
|
||||
.extensionsData(extensionsData)
|
||||
.annotations(new AnnotationInheritanceAwareAnnotatedElement(protoContext.annotations()))
|
||||
@@ -138,17 +154,70 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
|
||||
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
|
||||
try {
|
||||
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(dataClass, context);
|
||||
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(valueType, context);
|
||||
if (configEntry != null) {
|
||||
runAfterWeaveHooks(dataClass, configEntry, context);
|
||||
runAfterWeaveHooks(valueType, configEntry, context);
|
||||
return configEntry;
|
||||
}
|
||||
} 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(
|
||||
|
||||
@@ -7,10 +7,11 @@ import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||
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 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.isSimpleEntryForClass;
|
||||
@@ -18,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@NullUnmarked
|
||||
class CompoundPojoWeaverTest {
|
||||
|
||||
@Test
|
||||
@@ -30,8 +30,8 @@ class CompoundPojoWeaverTest {
|
||||
|
||||
PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build();
|
||||
|
||||
WeavingContext weavingContext = WeavingContext.builder()
|
||||
.weavingFunction(new WeavingContext.WeavingFn() {
|
||||
AtomicReference<WeavingContext.@Nullable WeavingFn> weavingFunction = new AtomicReference<>();
|
||||
weavingFunction.set(new WeavingContext.WeavingFn() {
|
||||
@Override
|
||||
public <T> ConfigEntry<T> weaveEntry(
|
||||
ActualType<T> valueType,
|
||||
@@ -43,14 +43,27 @@ class CompoundPojoWeaverTest {
|
||||
.annotations(valueType)
|
||||
.extensionsData(extensionsData.copy())
|
||||
.path(protoContext.path())
|
||||
.weavingFunction(protoContext.parent()::weaveEntry)
|
||||
.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()
|
||||
.weavingFunction(Objects.requireNonNull(weavingFunction.get()))
|
||||
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||
.annotations(Compound.class)
|
||||
.path(new String[0])
|
||||
|
||||
Reference in New Issue
Block a user