feat(core, serde-ext, weaver-pojo)!: Introduce string map weaving, extend addressable entry API
This commit is contained in:
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Breaking**@`core`: Added `getEntry` method to `AddressableStructuredConfigEntry`
|
||||||
|
that allows to get a subentry by its data key.
|
||||||
|
- `serde-extension`: Introduce a more generic `ReaderWriter` for `MutableStructuredConfigEntry`s.
|
||||||
|
- `weaver-pojo`: Make maps with string keys into their own weavable entry type.
|
||||||
|
|
||||||
## [0.8.1] - 2026-04-26
|
## [0.8.1] - 2026-04-26
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+4
@@ -1,5 +1,7 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface AddressableStructuredConfigEntry<T> extends StructuredConfigEntry<T> {
|
public interface AddressableStructuredConfigEntry<T> extends StructuredConfigEntry<T> {
|
||||||
@@ -9,5 +11,7 @@ public interface AddressableStructuredConfigEntry<T> extends StructuredConfigEnt
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable ConfigEntry<?> getEntry(String dataKey);
|
||||||
|
|
||||||
Object get(T value, String dataKey);
|
Object get(T value, String dataKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ public interface CompoundConfigEntry<T> extends MutableStructuredConfigEntry<T>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ConfigEntry<?> getEntry(String dataKey) {
|
||||||
|
return subEntries().get(dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void visitInOrder(ConfigEntryVisitor visitor) {
|
default void visitInOrder(ConfigEntryVisitor visitor) {
|
||||||
if (visitor.enterStructuredEntry(this)) {
|
if (visitor.enterStructuredEntry(this)) {
|
||||||
|
|||||||
+6
-1
@@ -1,7 +1,7 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.Arity;
|
import de.siphalor.tweed5.core.api.Arity;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -17,6 +17,11 @@ public interface NullableConfigEntry<T extends @Nullable Object> extends Address
|
|||||||
|
|
||||||
ConfigEntry<T> nonNullEntry();
|
ConfigEntry<T> nonNullEntry();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default @Nullable ConfigEntry<?> getEntry(String dataKey) {
|
||||||
|
return dataKey.equals(NON_NULL_KEY) ? nonNullEntry() : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void visitInOrder(ConfigEntryVisitor visitor) {
|
default void visitInOrder(ConfigEntryVisitor visitor) {
|
||||||
if (visitor.enterStructuredEntry(this)) {
|
if (visitor.enterStructuredEntry(this)) {
|
||||||
|
|||||||
+6
@@ -3,6 +3,7 @@ package de.siphalor.tweed5.serde.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.MutableStructuredConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
|
||||||
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
|
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
|
||||||
import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter;
|
||||||
@@ -79,6 +80,11 @@ public class TweedEntryReaderWriters {
|
|||||||
return (TweedEntryReaderWriter<C, @NonNull CollectionConfigEntry<T, C>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COLLECTION_READER_WRITER;
|
return (TweedEntryReaderWriter<C, @NonNull CollectionConfigEntry<T, C>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COLLECTION_READER_WRITER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, E extends MutableStructuredConfigEntry<T>> TweedEntryReaderWriter<T, E> mutableStructuredReaderWriter() {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (TweedEntryReaderWriter<T, E>) TweedEntryReaderWriterImpls.MUTABLE_STRUCTURED_READER_WRITER;
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> TweedEntryReaderWriter<T, CompoundConfigEntry<T>> compoundReaderWriter() {
|
public static <T> TweedEntryReaderWriter<T, CompoundConfigEntry<T>> compoundReaderWriter() {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (TweedEntryReaderWriter<T, @NonNull CompoundConfigEntry<T>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COMPOUND_READER_WRITER;
|
return (TweedEntryReaderWriter<T, @NonNull CompoundConfigEntry<T>>) (TweedEntryReaderWriter<?, ?>) TweedEntryReaderWriterImpls.COMPOUND_READER_WRITER;
|
||||||
|
|||||||
+16
-6
@@ -32,7 +32,8 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
|
|
||||||
public static final TweedEntryReaderWriter<Object, NullableConfigEntry<Object>> NULLABLE_READER_WRITER = new NullableReaderWriter<>();
|
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, MutableStructuredConfigEntry<Object>> MUTABLE_STRUCTURED_READER_WRITER = new MutableStructuredReaderWriter<>();
|
||||||
|
public static final TweedEntryReaderWriter<Object, CompoundConfigEntry<Object>> COMPOUND_READER_WRITER = new MutableStructuredReaderWriter<>();
|
||||||
|
|
||||||
public static final TweedEntryReaderWriter<Object, ConfigEntry<Object>> NOOP_READER_WRITER = new NoopReaderWriter();
|
public static final TweedEntryReaderWriter<Object, ConfigEntry<Object>> NOOP_READER_WRITER = new NoopReaderWriter();
|
||||||
|
|
||||||
@@ -219,11 +220,15 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CompoundReaderWriter<T> implements TweedEntryReaderWriter<T, CompoundConfigEntry<T>> {
|
public static class MutableStructuredReaderWriter<T, E extends MutableStructuredConfigEntry<T>>
|
||||||
|
implements TweedEntryReaderWriter<T, E> {
|
||||||
@Override
|
@Override
|
||||||
public TweedReadResult<T> read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) {
|
public TweedReadResult<T> read(
|
||||||
|
TweedDataReader reader,
|
||||||
|
E entry,
|
||||||
|
TweedReadContext context
|
||||||
|
) {
|
||||||
return requireToken(reader, TweedDataToken::isMapStart, "Expected map start", context).andThen(_token -> {
|
return requireToken(reader, TweedDataToken::isMapStart, "Expected map start", context).andThen(_token -> {
|
||||||
Map<String, ConfigEntry<?>> compoundEntries = entry.subEntries();
|
|
||||||
T compoundValue = entry.instantiateValue();
|
T compoundValue = entry.instantiateValue();
|
||||||
List<TweedReadIssue> issues = new ArrayList<>();
|
List<TweedReadIssue> issues = new ArrayList<>();
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -235,7 +240,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
String key = token.readAsString();
|
String key = token.readAsString();
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
|
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) entry.getEntry(key);
|
||||||
if (subEntry == null) {
|
if (subEntry == null) {
|
||||||
TweedReadResult<Object> noopResult = NOOP_READER_WRITER.read(reader, null, context);
|
TweedReadResult<Object> noopResult = NOOP_READER_WRITER.read(reader, null, context);
|
||||||
issues.addAll(Arrays.asList(noopResult.issues()));
|
issues.addAll(Arrays.asList(noopResult.issues()));
|
||||||
@@ -277,7 +282,12 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(TweedDataVisitor writer, @Nullable T value, CompoundConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
public void write(
|
||||||
|
TweedDataVisitor writer,
|
||||||
|
@Nullable T value,
|
||||||
|
E entry,
|
||||||
|
TweedWriteContext context
|
||||||
|
) throws TweedEntryWriteException, TweedDataWriteException {
|
||||||
requireNonNullWriteValue(value, context);
|
requireNonNullWriteValue(value, context);
|
||||||
|
|
||||||
writer.visitMapStart();
|
writer.visitMapStart();
|
||||||
|
|||||||
+2
@@ -3,6 +3,7 @@ package de.siphalor.tweed5.weaver.pojo.api.annotation;
|
|||||||
import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritance;
|
import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritance;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.CollectionPojoWeaver;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.CollectionPojoWeaver;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.StringMapPojoWeaver;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
@@ -13,6 +14,7 @@ import java.lang.annotation.Target;
|
|||||||
@AnnotationInheritance(passOn = PojoWeavingExtension.class)
|
@AnnotationInheritance(passOn = PojoWeavingExtension.class)
|
||||||
@PojoWeavingExtension(CompoundPojoWeaver.class)
|
@PojoWeavingExtension(CompoundPojoWeaver.class)
|
||||||
@PojoWeavingExtension(CollectionPojoWeaver.class)
|
@PojoWeavingExtension(CollectionPojoWeaver.class)
|
||||||
|
@PojoWeavingExtension(StringMapPojoWeaver.class)
|
||||||
@PojoWeavingExtension(TrivialPojoWeaver.class)
|
@PojoWeavingExtension(TrivialPojoWeaver.class)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.api.annotation;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableStringMapConfigEntry;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.TYPE_USE})
|
||||||
|
public @interface StringMapWeaving {
|
||||||
|
Class<? extends WeavableStringMapConfigEntry> entryClass() default WeavableStringMapConfigEntry.class;
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.api.entry;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
|
||||||
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.MutableStructuredConfigEntry;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public interface WeavableStringMapConfigEntry<V, M extends Map<String, V>> extends MutableStructuredConfigEntry<M> {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
TweedConstructFactory<WeavableStringMapConfigEntry> FACTORY =
|
||||||
|
TweedConstructFactory.builder(WeavableStringMapConfigEntry.class)
|
||||||
|
.typedArg(ConfigContainer.class)
|
||||||
|
.namedArg("mapClass", Class.class) // map class
|
||||||
|
.namedArg("mapConstructor", Supplier.class) // map constructor
|
||||||
|
.namedArg("valueEntry", ConfigEntry.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
+4
-5
@@ -9,8 +9,7 @@ import de.siphalor.tweed5.weaver.pojo.api.annotation.CollectionWeaving;
|
|||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.entry.CollectionConfigEntryImpl;
|
import de.siphalor.tweed5.weaver.pojo.impl.entry.CollectionConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.collection.CollectionWeavingConfig;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.config.CollectionWeavingConfig;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.collection.CollectionWeavingConfigImpl;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
@@ -21,7 +20,7 @@ import java.util.*;
|
|||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
public class CollectionPojoWeaver implements TweedPojoWeavingExtension {
|
public class CollectionPojoWeaver implements TweedPojoWeavingExtension {
|
||||||
private static final CollectionWeavingConfig DEFAULT_WEAVING_CONFIG = CollectionWeavingConfigImpl.builder()
|
private static final CollectionWeavingConfig DEFAULT_WEAVING_CONFIG = CollectionWeavingConfig.builder()
|
||||||
.collectionEntryClass(CollectionConfigEntryImpl.class)
|
.collectionEntryClass(CollectionConfigEntryImpl.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -80,7 +79,7 @@ public class CollectionPojoWeaver implements TweedPojoWeavingExtension {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CollectionWeavingConfigImpl.withOverrides(parent, local);
|
return CollectionWeavingConfig.withOverrides(parent, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable CollectionWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) {
|
private @Nullable CollectionWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) {
|
||||||
@@ -89,7 +88,7 @@ public class CollectionPojoWeaver implements TweedPojoWeavingExtension {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionWeavingConfigImpl.CollectionWeavingConfigImplBuilder builder = CollectionWeavingConfigImpl.builder();
|
CollectionWeavingConfig.CollectionWeavingConfigBuilder builder = CollectionWeavingConfig.builder();
|
||||||
if (annotation.entryClass() != null) {
|
if (annotation.entryClass() != null) {
|
||||||
builder.collectionEntryClass(annotation.entryClass());
|
builder.collectionEntryClass(annotation.entryClass());
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -15,8 +15,7 @@ import de.siphalor.tweed5.typeutils.api.type.TypeAnnotationLayer;
|
|||||||
import de.siphalor.tweed5.weaver.pojo.impl.entry.StaticPojoCompoundConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.impl.entry.StaticPojoCompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoClassIntrospector;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoClassIntrospector;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.config.CompoundWeavingConfig;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfigImpl;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
@@ -30,7 +29,7 @@ import java.util.stream.Collectors;
|
|||||||
* A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries.
|
* A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries.
|
||||||
*/
|
*/
|
||||||
public class CompoundPojoWeaver implements TweedPojoWeavingExtension {
|
public class CompoundPojoWeaver implements TweedPojoWeavingExtension {
|
||||||
private static final CompoundWeavingConfig DEFAULT_WEAVING_CONFIG = CompoundWeavingConfigImpl.builder()
|
private static final CompoundWeavingConfig DEFAULT_WEAVING_CONFIG = CompoundWeavingConfig.builder()
|
||||||
.compoundSourceNamingFormat(NamingFormats.camelCase())
|
.compoundSourceNamingFormat(NamingFormats.camelCase())
|
||||||
.compoundTargetNamingFormat(NamingFormats.camelCase())
|
.compoundTargetNamingFormat(NamingFormats.camelCase())
|
||||||
.compoundEntryClass(StaticPojoCompoundConfigEntry.class)
|
.compoundEntryClass(StaticPojoCompoundConfigEntry.class)
|
||||||
@@ -84,7 +83,7 @@ public class CompoundPojoWeaver implements TweedPojoWeavingExtension {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompoundWeavingConfigImpl.withOverrides(parent, local);
|
return CompoundWeavingConfig.withOverrides(parent, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable CompoundWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) {
|
private @Nullable CompoundWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) {
|
||||||
@@ -93,7 +92,7 @@ public class CompoundPojoWeaver implements TweedPojoWeavingExtension {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompoundWeavingConfigImpl.CompoundWeavingConfigImplBuilder builder = CompoundWeavingConfigImpl.builder();
|
CompoundWeavingConfig.CompoundWeavingConfigBuilder builder = CompoundWeavingConfig.builder();
|
||||||
builder.compoundSourceNamingFormat(NamingFormats.camelCase());
|
builder.compoundSourceNamingFormat(NamingFormats.camelCase());
|
||||||
if (!annotation.namingFormat().isEmpty()) {
|
if (!annotation.namingFormat().isEmpty()) {
|
||||||
builder.compoundTargetNamingFormat(getNamingFormatById(annotation.namingFormat()));
|
builder.compoundTargetNamingFormat(getNamingFormatById(annotation.namingFormat()));
|
||||||
|
|||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.StringMapWeaving;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableStringMapConfigEntry;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.impl.entry.StringMapConfigEntryImpl;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.config.StringMapWeavingConfig;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A weaver that weaves classes with the {@link StringMapWeaving} annotation as string map entries.
|
||||||
|
* The type must be a {@link Map} with {@link String} keys.
|
||||||
|
*/
|
||||||
|
public class StringMapPojoWeaver implements TweedPojoWeavingExtension {
|
||||||
|
private static final StringMapWeavingConfig DEFAULT_WEAVING_CONFIG = StringMapWeavingConfig.builder()
|
||||||
|
.stringMapEntryClass(StringMapConfigEntryImpl.class)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private PatchworkPartAccess<StringMapWeavingConfig> weavingConfigAccess;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(SetupContext context) {
|
||||||
|
this.weavingConfigAccess = context.registerWeavingContextExtensionData(StringMapWeavingConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
|
assert weavingConfigAccess != null;
|
||||||
|
|
||||||
|
List<ActualType<?>> mapTypeParams = valueType.getTypesOfSuperArguments(Map.class);
|
||||||
|
if (mapTypeParams == null || mapTypeParams.get(0).declaredType() != String.class) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringMapWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||||
|
Patchwork newExtensionsData = context.extensionsData().copy();
|
||||||
|
newExtensionsData.set(weavingConfigAccess, weavingConfig);
|
||||||
|
|
||||||
|
ActualType<?> valueTypeParam = mapTypeParams.get(1);
|
||||||
|
Supplier<Map<String, Object>> constructor = getMapConstructor(valueType);
|
||||||
|
|
||||||
|
ConfigEntry<?> valueEntry = context.weaveEntry(
|
||||||
|
valueTypeParam,
|
||||||
|
newExtensionsData,
|
||||||
|
ProtoWeavingContext.subContextFor(context, "value", valueTypeParam)
|
||||||
|
);
|
||||||
|
|
||||||
|
return WeavableStringMapConfigEntry.FACTORY
|
||||||
|
.construct(Objects.requireNonNull(weavingConfig.stringMapEntryClass()))
|
||||||
|
.typedArg(ConfigContainer.class, context.configContainer())
|
||||||
|
.namedArg("mapClass", valueType.declaredType())
|
||||||
|
.namedArg("mapConstructor", constructor)
|
||||||
|
.namedArg("valueEntry", valueEntry)
|
||||||
|
.finish();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PojoWeavingException("Exception occurred trying to weave string map for class " + valueType, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringMapWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||||
|
assert weavingConfigAccess != null;
|
||||||
|
|
||||||
|
StringMapWeavingConfig parent = context.extensionsData().get(weavingConfigAccess);
|
||||||
|
if (parent == null) {
|
||||||
|
parent = DEFAULT_WEAVING_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringMapWeavingConfig local = createWeavingConfigFromAnnotations(context.annotations());
|
||||||
|
if (local == null) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringMapWeavingConfig.withOverrides(parent, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable StringMapWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) {
|
||||||
|
StringMapWeaving annotation = annotations.getAnnotation(StringMapWeaving.class);
|
||||||
|
if (annotation == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringMapWeavingConfig.StringMapWeavingConfigBuilder builder = StringMapWeavingConfig.builder();
|
||||||
|
if (annotation.entryClass() != WeavableStringMapConfigEntry.class) {
|
||||||
|
builder.stringMapEntryClass(annotation.entryClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Supplier<Map<String, Object>> getMapConstructor(ActualType<?> type) {
|
||||||
|
Class<?> declaredType = type.declaredType();
|
||||||
|
if (declaredType == Map.class || declaredType == HashMap.class) {
|
||||||
|
return HashMap::new;
|
||||||
|
} else if (declaredType == LinkedHashMap.class) {
|
||||||
|
return LinkedHashMap::new;
|
||||||
|
} else if (declaredType == TreeMap.class) {
|
||||||
|
return TreeMap::new;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return findCompatibleConstructor(type);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
throw new PojoWeavingException("Could not find no-args constructor for " + type, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Supplier<Map<String, Object>> findCompatibleConstructor(ActualType<?> type) throws
|
||||||
|
NoSuchMethodException,
|
||||||
|
IllegalAccessException {
|
||||||
|
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||||
|
MethodHandle constructor = lookup.findConstructor(type.declaredType(), MethodType.methodType(void.class));
|
||||||
|
return () -> {
|
||||||
|
try {
|
||||||
|
return (Map<String, Object>) constructor.invoke();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.impl.entry;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.construct.api.ConstructParameter;
|
||||||
|
import de.siphalor.tweed5.core.api.Arity;
|
||||||
|
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.ConfigEntryVisitor;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.SubEntryKey;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableStringMapConfigEntry;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class StringMapConfigEntryImpl<V, M extends Map<String, V>>
|
||||||
|
extends BaseConfigEntry<M>
|
||||||
|
implements WeavableStringMapConfigEntry<V, M> {
|
||||||
|
private final Supplier<M> mapConstructor;
|
||||||
|
private final ConfigEntry<V> valueEntry;
|
||||||
|
|
||||||
|
public StringMapConfigEntryImpl(
|
||||||
|
ConfigContainer<?> container,
|
||||||
|
@ConstructParameter(name = "mapClass") Class<M> mapClass,
|
||||||
|
@ConstructParameter(name = "mapConstructor") Supplier<M> mapConstructor,
|
||||||
|
@ConstructParameter(name = "valueEntry") ConfigEntry<V> valueEntry
|
||||||
|
) {
|
||||||
|
super(container, mapClass);
|
||||||
|
this.mapConstructor = mapConstructor;
|
||||||
|
this.valueEntry = valueEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public M instantiateValue() {
|
||||||
|
return mapConstructor.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(M value, String dataKey, Object subValue) {
|
||||||
|
//noinspection unchecked
|
||||||
|
value.put(dataKey, (V) subValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ConfigEntry<?> getEntry(String dataKey) {
|
||||||
|
return valueEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(M value, String dataKey) {
|
||||||
|
return value.get(dataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ConfigEntry<?>> subEntries() {
|
||||||
|
return Collections.singletonMap(":value", valueEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInOrder(ConfigEntryVisitor visitor) {
|
||||||
|
if (visitor.enterStructuredEntry(this)) {
|
||||||
|
if (visitor.enterStructuredSubEntry(":value", Arity.ANY)) {
|
||||||
|
valueEntry.visitInOrder(visitor);
|
||||||
|
visitor.leaveStructuredSubEntry(":value", Arity.ANY);
|
||||||
|
}
|
||||||
|
visitor.leaveStructuredEntry(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInOrder(ConfigEntryValueVisitor visitor, M value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (visitor.enterStructuredEntry(this, value)) {
|
||||||
|
for (Map.Entry<String, V> entry : value.entrySet()) {
|
||||||
|
SubEntryKey subEntryKey = SubEntryKey.addressable(":value", entry.getKey(), entry.getKey());
|
||||||
|
if (visitor.enterSubEntry(subEntryKey)) {
|
||||||
|
valueEntry.visitInOrder(visitor, entry.getValue());
|
||||||
|
visitor.leaveSubEntry(subEntryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visitor.leaveStructuredEntry(this, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public M deepCopy(M value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
M copy = mapConstructor.get();
|
||||||
|
for (Map.Entry<String, V> entry : value.entrySet()) {
|
||||||
|
copy.put(entry.getKey(), valueEntry.deepCopy(entry.getValue()));
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.collection;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
public interface CollectionWeavingConfig {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Nullable Class<? extends WeavableCollectionConfigEntry> collectionEntryClass();
|
|
||||||
}
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
@NullMarked
|
|
||||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.collection;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.NullMarked;
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.compound;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
public interface CompoundWeavingConfig {
|
|
||||||
@Nullable NamingFormat compoundSourceNamingFormat();
|
|
||||||
|
|
||||||
@Nullable NamingFormat compoundTargetNamingFormat();
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Nullable Class<? extends WeavableCompoundConfigEntry> compoundEntryClass();
|
|
||||||
}
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
@NullMarked
|
|
||||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.compound;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.NullMarked;
|
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.collection;
|
package de.siphalor.tweed5.weaver.pojo.impl.weaving.config;
|
||||||
|
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -7,13 +7,13 @@ import org.jspecify.annotations.Nullable;
|
|||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
@Value
|
@Value
|
||||||
public class CollectionWeavingConfigImpl implements CollectionWeavingConfig {
|
public class CollectionWeavingConfig {
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@Nullable Class<? extends WeavableCollectionConfigEntry> collectionEntryClass;
|
@Nullable Class<? extends WeavableCollectionConfigEntry> collectionEntryClass;
|
||||||
|
|
||||||
public static CollectionWeavingConfigImpl withOverrides(CollectionWeavingConfig self, CollectionWeavingConfig overrides) {
|
public static CollectionWeavingConfig withOverrides(CollectionWeavingConfig self, CollectionWeavingConfig overrides) {
|
||||||
return CollectionWeavingConfigImpl.builder()
|
return CollectionWeavingConfig.builder()
|
||||||
.collectionEntryClass(overrides.collectionEntryClass() != null ? overrides.collectionEntryClass() : self.collectionEntryClass())
|
.collectionEntryClass(overrides.collectionEntryClass() != null ? overrides.collectionEntryClass() : self.collectionEntryClass())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.compound;
|
package de.siphalor.tweed5.weaver.pojo.impl.weaving.config;
|
||||||
|
|
||||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
||||||
@@ -8,15 +8,15 @@ import org.jspecify.annotations.Nullable;
|
|||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
@Value
|
@Value
|
||||||
public class CompoundWeavingConfigImpl implements CompoundWeavingConfig {
|
public class CompoundWeavingConfig {
|
||||||
|
|
||||||
@Nullable NamingFormat compoundSourceNamingFormat;
|
@Nullable NamingFormat compoundSourceNamingFormat;
|
||||||
@Nullable NamingFormat compoundTargetNamingFormat;
|
@Nullable NamingFormat compoundTargetNamingFormat;
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@Nullable Class<? extends WeavableCompoundConfigEntry> compoundEntryClass;
|
@Nullable Class<? extends WeavableCompoundConfigEntry> compoundEntryClass;
|
||||||
|
|
||||||
public static CompoundWeavingConfigImpl withOverrides(CompoundWeavingConfig self, CompoundWeavingConfig overrides) {
|
public static CompoundWeavingConfig withOverrides(CompoundWeavingConfig self, CompoundWeavingConfig overrides) {
|
||||||
return CompoundWeavingConfigImpl.builder()
|
return CompoundWeavingConfig.builder()
|
||||||
.compoundSourceNamingFormat(overrides.compoundSourceNamingFormat() != null ? overrides.compoundSourceNamingFormat() : self.compoundSourceNamingFormat())
|
.compoundSourceNamingFormat(overrides.compoundSourceNamingFormat() != null ? overrides.compoundSourceNamingFormat() : self.compoundSourceNamingFormat())
|
||||||
.compoundTargetNamingFormat(overrides.compoundTargetNamingFormat() != null ? overrides.compoundTargetNamingFormat() : self.compoundTargetNamingFormat())
|
.compoundTargetNamingFormat(overrides.compoundTargetNamingFormat() != null ? overrides.compoundTargetNamingFormat() : self.compoundTargetNamingFormat())
|
||||||
.compoundEntryClass(overrides.compoundEntryClass() != null ? overrides.compoundEntryClass() : self.compoundEntryClass())
|
.compoundEntryClass(overrides.compoundEntryClass() != null ? overrides.compoundEntryClass() : self.compoundEntryClass())
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.impl.weaving.config;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableStringMapConfigEntry;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public class StringMapWeavingConfig {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Nullable Class<? extends WeavableStringMapConfigEntry> stringMapEntryClass;
|
||||||
|
|
||||||
|
public static StringMapWeavingConfig withOverrides(StringMapWeavingConfig self, StringMapWeavingConfig overrides) {
|
||||||
|
return StringMapWeavingConfig.builder()
|
||||||
|
.stringMapEntryClass(overrides.stringMapEntryClass() != null ? overrides.stringMapEntryClass() : self.stringMapEntryClass())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
@NullMarked
|
||||||
|
package de.siphalor.tweed5.weaver.pojo.impl.weaving.config;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
|
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.StringMapWeaving;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||||
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isStringMapEntryForClass;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
class StringMapPojoWeaverTest {
|
||||||
|
|
||||||
|
private StringMapPojoWeaver stringMapWeaver;
|
||||||
|
private PatchworkFactory weavingContextExtensionDataFactory;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
PatchworkFactory.Builder builder = PatchworkFactory.builder();
|
||||||
|
stringMapWeaver = new StringMapPojoWeaver();
|
||||||
|
stringMapWeaver.setup(builder::registerPart);
|
||||||
|
weavingContextExtensionDataFactory = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeavingContext.WeavingFn createWeavingFn(AtomicReference<WeavingContext.@Nullable WeavingFn> ref) {
|
||||||
|
WeavingContext.WeavingFn fn = 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(ref.get()))
|
||||||
|
.build();
|
||||||
|
return Objects.requireNonNullElseGet(
|
||||||
|
stringMapWeaver.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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ref.set(fn);
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void weaveStringToIntegerMap() {
|
||||||
|
AtomicReference<WeavingContext.@Nullable WeavingFn> weavingFnRef = new AtomicReference<>();
|
||||||
|
WeavingContext.WeavingFn weavingFn = createWeavingFn(weavingFnRef);
|
||||||
|
|
||||||
|
WeavingContext weavingContext = WeavingContext.builder()
|
||||||
|
.weavingFunction(weavingFn)
|
||||||
|
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||||
|
.annotations(StringToIntegerMap.class)
|
||||||
|
.path(new String[0])
|
||||||
|
.configContainer(mock(ConfigContainer.class))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ConfigEntry<StringToIntegerMap> resultEntry = stringMapWeaver.weaveEntry(
|
||||||
|
ActualType.ofClass(StringToIntegerMap.class),
|
||||||
|
weavingContext
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(resultEntry).satisfies(isStringMapEntryForClass(StringToIntegerMap.class, mapEntry ->
|
||||||
|
assertThat(mapEntry.subEntries())
|
||||||
|
.hasEntrySatisfying(":value", isSimpleEntryForClass(Integer.class))
|
||||||
|
.hasSize(1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void weaveStringToStringMap() {
|
||||||
|
AtomicReference<WeavingContext.@Nullable WeavingFn> weavingFnRef = new AtomicReference<>();
|
||||||
|
WeavingContext.WeavingFn weavingFn = createWeavingFn(weavingFnRef);
|
||||||
|
|
||||||
|
WeavingContext weavingContext = WeavingContext.builder()
|
||||||
|
.weavingFunction(weavingFn)
|
||||||
|
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||||
|
.annotations(StringToStringLinkedMap.class)
|
||||||
|
.path(new String[0])
|
||||||
|
.configContainer(mock(ConfigContainer.class))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ConfigEntry<StringToStringLinkedMap> resultEntry = stringMapWeaver.weaveEntry(
|
||||||
|
ActualType.ofClass(StringToStringLinkedMap.class),
|
||||||
|
weavingContext
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(resultEntry).satisfies(isStringMapEntryForClass(StringToStringLinkedMap.class, mapEntry ->
|
||||||
|
assertThat(mapEntry.subEntries())
|
||||||
|
.hasEntrySatisfying(":value", isSimpleEntryForClass(String.class))
|
||||||
|
.hasSize(1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNullWithoutAnnotation() {
|
||||||
|
WeavingContext weavingContext = WeavingContext.builder()
|
||||||
|
.weavingFunction(mock(WeavingContext.WeavingFn.class))
|
||||||
|
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||||
|
.annotations(UnannotatedStringMap.class)
|
||||||
|
.path(new String[0])
|
||||||
|
.configContainer(mock(ConfigContainer.class))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ConfigEntry<UnannotatedStringMap> resultEntry = stringMapWeaver.weaveEntry(
|
||||||
|
ActualType.ofClass(UnannotatedStringMap.class),
|
||||||
|
weavingContext
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(resultEntry).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNullForNonStringKey() {
|
||||||
|
WeavingContext weavingContext = WeavingContext.builder()
|
||||||
|
.weavingFunction(mock(WeavingContext.WeavingFn.class))
|
||||||
|
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||||
|
.annotations(IntegerKeyMap.class)
|
||||||
|
.path(new String[0])
|
||||||
|
.configContainer(mock(ConfigContainer.class))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ConfigEntry<IntegerKeyMap> resultEntry = stringMapWeaver.weaveEntry(
|
||||||
|
ActualType.ofClass(IntegerKeyMap.class),
|
||||||
|
weavingContext
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(resultEntry).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringMapWeaving
|
||||||
|
public static class StringToIntegerMap extends HashMap<String, Integer> {}
|
||||||
|
|
||||||
|
@StringMapWeaving
|
||||||
|
public static class StringToStringLinkedMap extends LinkedHashMap<String, String> {}
|
||||||
|
|
||||||
|
public static class UnannotatedStringMap extends HashMap<String, Integer> {}
|
||||||
|
|
||||||
|
@StringMapWeaving
|
||||||
|
public static class IntegerKeyMap extends HashMap<Integer, String> {}
|
||||||
|
}
|
||||||
+18
@@ -4,6 +4,7 @@ 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.SimpleConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableStringMapConfigEntry;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -35,6 +36,23 @@ public class ConfigEntryAssertions {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static Consumer<Object> isStringMapEntryForClass(
|
||||||
|
Class<?> mapClass,
|
||||||
|
Consumer<WeavableStringMapConfigEntry<?, ?>> condition
|
||||||
|
) {
|
||||||
|
return object -> assertThat(object)
|
||||||
|
.as("Should be a string map config entry for class " + mapClass.getName())
|
||||||
|
.asInstanceOf(type(WeavableStringMapConfigEntry.class))
|
||||||
|
.as("String map entry for class " + mapClass.getSimpleName())
|
||||||
|
.satisfies(
|
||||||
|
mapEntry -> assertThat(mapEntry.valueClass())
|
||||||
|
.as("Value class of string map entry should match")
|
||||||
|
.isEqualTo(mapClass),
|
||||||
|
condition::accept
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static Consumer<Object> isCollectionEntryForClass(
|
public static Consumer<Object> isCollectionEntryForClass(
|
||||||
Class<?> collectionClass,
|
Class<?> collectionClass,
|
||||||
Consumer<CollectionConfigEntry<?, ?>> condition
|
Consumer<CollectionConfigEntry<?, ?>> condition
|
||||||
|
|||||||
Reference in New Issue
Block a user