From 353c5126bcdfc3aec1cbbf54752a4fcc837a7770 Mon Sep 17 00:00:00 2001 From: Siphalor Date: Sat, 2 May 2026 18:58:17 +0200 Subject: [PATCH] feat(core, serde-ext, weaver-pojo)!: Introduce string map weaving, extend addressable entry API --- CHANGELOG.md | 9 + .../AddressableStructuredConfigEntry.java | 4 + .../core/api/entry/CompoundConfigEntry.java | 5 + .../core/api/entry/NullableConfigEntry.java | 7 +- .../readwrite/TweedEntryReaderWriters.java | 6 + .../impl/TweedEntryReaderWriterImpls.java | 22 ++- .../annotation/DefaultWeavingExtensions.java | 2 + .../pojo/api/annotation/StringMapWeaving.java | 14 ++ .../entry/WeavableStringMapConfigEntry.java | 20 +++ .../api/weaving/CollectionPojoWeaver.java | 9 +- .../pojo/api/weaving/CompoundPojoWeaver.java | 9 +- .../pojo/api/weaving/StringMapPojoWeaver.java | 135 ++++++++++++++ .../impl/entry/StringMapConfigEntryImpl.java | 100 +++++++++++ .../collection/CollectionWeavingConfig.java | 9 - .../impl/weaving/collection/package-info.java | 4 - .../compound/CompoundWeavingConfig.java | 14 -- .../impl/weaving/compound/package-info.java | 4 - .../CollectionWeavingConfig.java} | 8 +- .../CompoundWeavingConfig.java} | 8 +- .../config/StringMapWeavingConfig.java | 20 +++ .../impl/weaving/config/package-info.java | 4 + .../api/weaving/StringMapPojoWeaverTest.java | 170 ++++++++++++++++++ .../pojo/test/ConfigEntryAssertions.java | 18 ++ 23 files changed, 545 insertions(+), 56 deletions(-) create mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/StringMapWeaving.java create mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/entry/WeavableStringMapConfigEntry.java create mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaver.java create mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StringMapConfigEntryImpl.java delete mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfig.java delete mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/package-info.java delete mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfig.java delete mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/package-info.java rename tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/{collection/CollectionWeavingConfigImpl.java => config/CollectionWeavingConfig.java} (58%) rename tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/{compound/CompoundWeavingConfigImpl.java => config/CompoundWeavingConfig.java} (75%) create mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/StringMapWeavingConfig.java create mode 100644 tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/package-info.java create mode 100644 tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaverTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e7db4..cb3410a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/), 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 ### Fixed diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java index 8c55cbe..be60814 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/AddressableStructuredConfigEntry.java @@ -1,5 +1,7 @@ package de.siphalor.tweed5.core.api.entry; +import org.jspecify.annotations.Nullable; + import java.util.function.Consumer; public interface AddressableStructuredConfigEntry extends StructuredConfigEntry { @@ -9,5 +11,7 @@ public interface AddressableStructuredConfigEntry extends StructuredConfigEnt return this; } + @Nullable ConfigEntry getEntry(String dataKey); + Object get(T value, String dataKey); } diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java index df14bdd..6e6c087 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java @@ -11,6 +11,11 @@ public interface CompoundConfigEntry extends MutableStructuredConfigEntry return this; } + @Override + default ConfigEntry getEntry(String dataKey) { + return subEntries().get(dataKey); + } + @Override default void visitInOrder(ConfigEntryVisitor visitor) { if (visitor.enterStructuredEntry(this)) { diff --git a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java index bcac983..8bc61ed 100644 --- a/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java +++ b/tweed5/core/src/main/java/de/siphalor/tweed5/core/api/entry/NullableConfigEntry.java @@ -1,7 +1,7 @@ package de.siphalor.tweed5.core.api.entry; import de.siphalor.tweed5.core.api.Arity; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import java.util.function.Consumer; @@ -17,6 +17,11 @@ public interface NullableConfigEntry extends Address ConfigEntry nonNullEntry(); + @Override + default @Nullable ConfigEntry getEntry(String dataKey) { + return dataKey.equals(NON_NULL_KEY) ? nonNullEntry() : null; + } + @Override default void visitInOrder(ConfigEntryVisitor visitor) { if (visitor.enterStructuredEntry(this)) { diff --git a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/readwrite/TweedEntryReaderWriters.java b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/readwrite/TweedEntryReaderWriters.java index ddce881..9bf026c 100644 --- a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/readwrite/TweedEntryReaderWriters.java +++ b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/api/readwrite/TweedEntryReaderWriters.java @@ -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.CompoundConfigEntry; 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.serde.extension.api.TweedEntryReader; import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter; @@ -79,6 +80,11 @@ public class TweedEntryReaderWriters { return (TweedEntryReaderWriter>) (TweedEntryReaderWriter) TweedEntryReaderWriterImpls.COLLECTION_READER_WRITER; } + public static > TweedEntryReaderWriter mutableStructuredReaderWriter() { + //noinspection unchecked + return (TweedEntryReaderWriter) TweedEntryReaderWriterImpls.MUTABLE_STRUCTURED_READER_WRITER; + } + public static TweedEntryReaderWriter> compoundReaderWriter() { //noinspection unchecked return (TweedEntryReaderWriter>) (TweedEntryReaderWriter) TweedEntryReaderWriterImpls.COMPOUND_READER_WRITER; diff --git a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java index e6c388a..7ea70ad 100644 --- a/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java +++ b/tweed5/serde-extension/src/main/java/de/siphalor/tweed5/serde/extension/impl/TweedEntryReaderWriterImpls.java @@ -32,7 +32,8 @@ public class TweedEntryReaderWriterImpls { public static final TweedEntryReaderWriter> NULLABLE_READER_WRITER = new NullableReaderWriter<>(); public static final TweedEntryReaderWriter, CollectionConfigEntry>> COLLECTION_READER_WRITER = new CollectionReaderWriter<>(); - public static final TweedEntryReaderWriter> COMPOUND_READER_WRITER = new CompoundReaderWriter<>(); + public static final TweedEntryReaderWriter> MUTABLE_STRUCTURED_READER_WRITER = new MutableStructuredReaderWriter<>(); + public static final TweedEntryReaderWriter> COMPOUND_READER_WRITER = new MutableStructuredReaderWriter<>(); public static final TweedEntryReaderWriter> NOOP_READER_WRITER = new NoopReaderWriter(); @@ -219,11 +220,15 @@ public class TweedEntryReaderWriterImpls { } } - public static class CompoundReaderWriter implements TweedEntryReaderWriter> { + public static class MutableStructuredReaderWriter> + implements TweedEntryReaderWriter { @Override - public TweedReadResult read(TweedDataReader reader, CompoundConfigEntry entry, TweedReadContext context) { + public TweedReadResult read( + TweedDataReader reader, + E entry, + TweedReadContext context + ) { return requireToken(reader, TweedDataToken::isMapStart, "Expected map start", context).andThen(_token -> { - Map> compoundEntries = entry.subEntries(); T compoundValue = entry.instantiateValue(); List issues = new ArrayList<>(); while (true) { @@ -235,7 +240,7 @@ public class TweedEntryReaderWriterImpls { String key = token.readAsString(); //noinspection unchecked - ConfigEntry subEntry = (ConfigEntry) compoundEntries.get(key); + ConfigEntry subEntry = (ConfigEntry) entry.getEntry(key); if (subEntry == null) { TweedReadResult noopResult = NOOP_READER_WRITER.read(reader, null, context); issues.addAll(Arrays.asList(noopResult.issues())); @@ -277,7 +282,12 @@ public class TweedEntryReaderWriterImpls { } @Override - public void write(TweedDataVisitor writer, @Nullable T value, CompoundConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { + public void write( + TweedDataVisitor writer, + @Nullable T value, + E entry, + TweedWriteContext context + ) throws TweedEntryWriteException, TweedDataWriteException { requireNonNullWriteValue(value, context); writer.visitMapStart(); diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/DefaultWeavingExtensions.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/DefaultWeavingExtensions.java index 883f2c3..e3d4506 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/DefaultWeavingExtensions.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/DefaultWeavingExtensions.java @@ -3,6 +3,7 @@ package de.siphalor.tweed5.weaver.pojo.api.annotation; import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritance; 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.StringMapPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver; import java.lang.annotation.ElementType; @@ -13,6 +14,7 @@ import java.lang.annotation.Target; @AnnotationInheritance(passOn = PojoWeavingExtension.class) @PojoWeavingExtension(CompoundPojoWeaver.class) @PojoWeavingExtension(CollectionPojoWeaver.class) +@PojoWeavingExtension(StringMapPojoWeaver.class) @PojoWeavingExtension(TrivialPojoWeaver.class) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/StringMapWeaving.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/StringMapWeaving.java new file mode 100644 index 0000000..05c6dc6 --- /dev/null +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/annotation/StringMapWeaving.java @@ -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 entryClass() default WeavableStringMapConfigEntry.class; +} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/entry/WeavableStringMapConfigEntry.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/entry/WeavableStringMapConfigEntry.java new file mode 100644 index 0000000..b5baec9 --- /dev/null +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/entry/WeavableStringMapConfigEntry.java @@ -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> extends MutableStructuredConfigEntry { + @SuppressWarnings("rawtypes") + TweedConstructFactory 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(); +} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CollectionPojoWeaver.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CollectionPojoWeaver.java index 6277dc6..38858c7 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CollectionPojoWeaver.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CollectionPojoWeaver.java @@ -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.impl.entry.CollectionConfigEntryImpl; 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.collection.CollectionWeavingConfigImpl; +import de.siphalor.tweed5.weaver.pojo.impl.weaving.config.CollectionWeavingConfig; import org.jspecify.annotations.Nullable; import java.lang.invoke.MethodHandle; @@ -21,7 +20,7 @@ import java.util.*; import java.util.function.IntFunction; 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) .build(); @@ -80,7 +79,7 @@ public class CollectionPojoWeaver implements TweedPojoWeavingExtension { return parent; } - return CollectionWeavingConfigImpl.withOverrides(parent, local); + return CollectionWeavingConfig.withOverrides(parent, local); } private @Nullable CollectionWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) { @@ -89,7 +88,7 @@ public class CollectionPojoWeaver implements TweedPojoWeavingExtension { return null; } - CollectionWeavingConfigImpl.CollectionWeavingConfigImplBuilder builder = CollectionWeavingConfigImpl.builder(); + CollectionWeavingConfig.CollectionWeavingConfigBuilder builder = CollectionWeavingConfig.builder(); if (annotation.entryClass() != null) { builder.collectionEntryClass(annotation.entryClass()); } diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaver.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaver.java index 6588540..db12a1d 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaver.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaver.java @@ -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.weaving.PojoClassIntrospector; 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.compound.CompoundWeavingConfigImpl; +import de.siphalor.tweed5.weaver.pojo.impl.weaving.config.CompoundWeavingConfig; import org.jspecify.annotations.Nullable; 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. */ 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()) .compoundTargetNamingFormat(NamingFormats.camelCase()) .compoundEntryClass(StaticPojoCompoundConfigEntry.class) @@ -84,7 +83,7 @@ public class CompoundPojoWeaver implements TweedPojoWeavingExtension { return parent; } - return CompoundWeavingConfigImpl.withOverrides(parent, local); + return CompoundWeavingConfig.withOverrides(parent, local); } private @Nullable CompoundWeavingConfig createWeavingConfigFromAnnotations(AnnotatedElement annotations) { @@ -93,7 +92,7 @@ public class CompoundPojoWeaver implements TweedPojoWeavingExtension { return null; } - CompoundWeavingConfigImpl.CompoundWeavingConfigImplBuilder builder = CompoundWeavingConfigImpl.builder(); + CompoundWeavingConfig.CompoundWeavingConfigBuilder builder = CompoundWeavingConfig.builder(); builder.compoundSourceNamingFormat(NamingFormats.camelCase()); if (!annotation.namingFormat().isEmpty()) { builder.compoundTargetNamingFormat(getNamingFormatById(annotation.namingFormat())); diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaver.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaver.java new file mode 100644 index 0000000..fb555e4 --- /dev/null +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaver.java @@ -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 weavingConfigAccess; + + @Override + public void setup(SetupContext context) { + this.weavingConfigAccess = context.registerWeavingContextExtensionData(StringMapWeavingConfig.class); + } + + @SuppressWarnings({"unchecked"}) + @Override + public @Nullable ConfigEntry weaveEntry(ActualType valueType, WeavingContext context) { + assert weavingConfigAccess != null; + + List> 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> 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> 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> 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) constructor.invoke(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } +} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StringMapConfigEntryImpl.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StringMapConfigEntryImpl.java new file mode 100644 index 0000000..663e31c --- /dev/null +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StringMapConfigEntryImpl.java @@ -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> + extends BaseConfigEntry + implements WeavableStringMapConfigEntry { + private final Supplier mapConstructor; + private final ConfigEntry valueEntry; + + public StringMapConfigEntryImpl( + ConfigContainer container, + @ConstructParameter(name = "mapClass") Class mapClass, + @ConstructParameter(name = "mapConstructor") Supplier mapConstructor, + @ConstructParameter(name = "valueEntry") ConfigEntry 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> 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 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 entry : value.entrySet()) { + copy.put(entry.getKey(), valueEntry.deepCopy(entry.getValue())); + } + return copy; + } +} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfig.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfig.java deleted file mode 100644 index 33d0425..0000000 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfig.java +++ /dev/null @@ -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 collectionEntryClass(); -} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/package-info.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/package-info.java deleted file mode 100644 index e1095d8..0000000 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package de.siphalor.tweed5.weaver.pojo.impl.weaving.collection; - -import org.jspecify.annotations.NullMarked; diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfig.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfig.java deleted file mode 100644 index ebeb9cf..0000000 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfig.java +++ /dev/null @@ -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 compoundEntryClass(); -} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/package-info.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/package-info.java deleted file mode 100644 index 528e67e..0000000 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package de.siphalor.tweed5.weaver.pojo.impl.weaving.compound; - -import org.jspecify.annotations.NullMarked; diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfigImpl.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/CollectionWeavingConfig.java similarity index 58% rename from tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfigImpl.java rename to tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/CollectionWeavingConfig.java index 802d8f5..00012e9 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/collection/CollectionWeavingConfigImpl.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/CollectionWeavingConfig.java @@ -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 lombok.Builder; @@ -7,13 +7,13 @@ import org.jspecify.annotations.Nullable; @Builder @Value -public class CollectionWeavingConfigImpl implements CollectionWeavingConfig { +public class CollectionWeavingConfig { @SuppressWarnings("rawtypes") @Nullable Class collectionEntryClass; - public static CollectionWeavingConfigImpl withOverrides(CollectionWeavingConfig self, CollectionWeavingConfig overrides) { - return CollectionWeavingConfigImpl.builder() + public static CollectionWeavingConfig withOverrides(CollectionWeavingConfig self, CollectionWeavingConfig overrides) { + return CollectionWeavingConfig.builder() .collectionEntryClass(overrides.collectionEntryClass() != null ? overrides.collectionEntryClass() : self.collectionEntryClass()) .build(); } diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfigImpl.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/CompoundWeavingConfig.java similarity index 75% rename from tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfigImpl.java rename to tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/CompoundWeavingConfig.java index d09b0a2..780f39c 100644 --- a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/compound/CompoundWeavingConfigImpl.java +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/CompoundWeavingConfig.java @@ -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.weaver.pojo.api.entry.WeavableCompoundConfigEntry; @@ -8,15 +8,15 @@ import org.jspecify.annotations.Nullable; @Builder @Value -public class CompoundWeavingConfigImpl implements CompoundWeavingConfig { +public class CompoundWeavingConfig { @Nullable NamingFormat compoundSourceNamingFormat; @Nullable NamingFormat compoundTargetNamingFormat; @SuppressWarnings("rawtypes") @Nullable Class compoundEntryClass; - public static CompoundWeavingConfigImpl withOverrides(CompoundWeavingConfig self, CompoundWeavingConfig overrides) { - return CompoundWeavingConfigImpl.builder() + public static CompoundWeavingConfig withOverrides(CompoundWeavingConfig self, CompoundWeavingConfig overrides) { + return CompoundWeavingConfig.builder() .compoundSourceNamingFormat(overrides.compoundSourceNamingFormat() != null ? overrides.compoundSourceNamingFormat() : self.compoundSourceNamingFormat()) .compoundTargetNamingFormat(overrides.compoundTargetNamingFormat() != null ? overrides.compoundTargetNamingFormat() : self.compoundTargetNamingFormat()) .compoundEntryClass(overrides.compoundEntryClass() != null ? overrides.compoundEntryClass() : self.compoundEntryClass()) diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/StringMapWeavingConfig.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/StringMapWeavingConfig.java new file mode 100644 index 0000000..9266881 --- /dev/null +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/StringMapWeavingConfig.java @@ -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 stringMapEntryClass; + + public static StringMapWeavingConfig withOverrides(StringMapWeavingConfig self, StringMapWeavingConfig overrides) { + return StringMapWeavingConfig.builder() + .stringMapEntryClass(overrides.stringMapEntryClass() != null ? overrides.stringMapEntryClass() : self.stringMapEntryClass()) + .build(); + } +} diff --git a/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/package-info.java b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/package-info.java new file mode 100644 index 0000000..0010168 --- /dev/null +++ b/tweed5/weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/config/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.siphalor.tweed5.weaver.pojo.impl.weaving.config; + +import org.jspecify.annotations.NullMarked; diff --git a/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaverTest.java b/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaverTest.java new file mode 100644 index 0000000..01f490d --- /dev/null +++ b/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/StringMapPojoWeaverTest.java @@ -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 ref) { + WeavingContext.WeavingFn fn = new WeavingContext.WeavingFn() { + @Override + public ConfigEntry weaveEntry( + ActualType 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 ConfigEntry weavePseudoEntry( + WeavingContext parentContext, + String pseudoEntryName, + Patchwork extensionsData + ) { + assert false; + return null; + } + }; + ref.set(fn); + return fn; + } + + @Test + void weaveStringToIntegerMap() { + AtomicReference 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 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 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 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 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 resultEntry = stringMapWeaver.weaveEntry( + ActualType.ofClass(IntegerKeyMap.class), + weavingContext + ); + + assertThat(resultEntry).isNull(); + } + + @StringMapWeaving + public static class StringToIntegerMap extends HashMap {} + + @StringMapWeaving + public static class StringToStringLinkedMap extends LinkedHashMap {} + + public static class UnannotatedStringMap extends HashMap {} + + @StringMapWeaving + public static class IntegerKeyMap extends HashMap {} +} diff --git a/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/test/ConfigEntryAssertions.java b/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/test/ConfigEntryAssertions.java index 2a37164..06f4a86 100644 --- a/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/test/ConfigEntryAssertions.java +++ b/tweed5/weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/test/ConfigEntryAssertions.java @@ -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.ConfigEntry; import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; +import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableStringMapConfigEntry; import java.util.function.Consumer; @@ -35,6 +36,23 @@ public class ConfigEntryAssertions { ); } + @SuppressWarnings("rawtypes") + public static Consumer isStringMapEntryForClass( + Class mapClass, + Consumer> 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 isCollectionEntryForClass( Class collectionClass, Consumer> condition