diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94671bc..9d12bd0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] assertj = "3.26.3" -asm = "9.7" autoservice = "1.1.1" java-main = "8" java-test = "21" @@ -16,8 +15,6 @@ lombok = { id = "io.freefair.lombok", version = "8.13.1" } [libraries] assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" } -asm-commons = { group = "org.ow2.asm", name = "asm-commons", version.ref = "asm" } -asm-core = { group = "org.ow2.asm", name = "asm", version.ref = "asm" } autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" } autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" } jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/ConfigContainer.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/ConfigContainer.java index c899e20..70a0259 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/ConfigContainer.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/ConfigContainer.java @@ -1,13 +1,12 @@ package de.siphalor.tweed5.core.api.container; import de.siphalor.tweed5.construct.api.TweedConstructFactory; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; +import de.siphalor.tweed5.patchwork.api.Patchwork; +import org.jspecify.annotations.Nullable; import java.util.Collection; -import java.util.Map; import java.util.Optional; /** @@ -16,7 +15,7 @@ import java.util.Optional; * @param The class that the config tree represents * @see ConfigContainerSetupPhase */ -public interface ConfigContainer { +public interface ConfigContainer { @SuppressWarnings("rawtypes") TweedConstructFactory FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build(); @@ -36,10 +35,9 @@ public interface ConfigContainer { void attachTree(ConfigEntry rootEntry); - EntryExtensionsData createExtensionsData(); + Patchwork createExtensionsData(); void initialize(); ConfigEntry rootEntry(); - Map, ? extends RegisteredExtensionData> entryDataExtensions(); } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/package-info.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/package-info.java new file mode 100644 index 0000000..d219e2f --- /dev/null +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/container/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.siphalor.tweed5.core.api.container; + +import org.jspecify.annotations.NullMarked; diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/BaseConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/BaseConfigEntry.java index acf72d6..5abaa73 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/BaseConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/BaseConfigEntry.java @@ -1,14 +1,14 @@ package de.siphalor.tweed5.core.api.entry; import de.siphalor.tweed5.core.api.container.ConfigContainer; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; +import de.siphalor.tweed5.patchwork.api.Patchwork; import lombok.Getter; @Getter public abstract class BaseConfigEntry implements ConfigEntry { private final ConfigContainer container; private final Class valueClass; - private final EntryExtensionsData extensionsData; + private final Patchwork extensionsData; public BaseConfigEntry(ConfigContainer container, Class valueClass) { this.container = container; diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java index 1d7ab5f..5b4914e 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java @@ -1,8 +1,15 @@ package de.siphalor.tweed5.core.api.entry; import java.util.Collection; +import java.util.function.Consumer; public interface CollectionConfigEntry> extends ConfigEntry { + @Override + default CollectionConfigEntry apply(Consumer> function) { + ConfigEntry.super.apply(function); + return this; + } + ConfigEntry elementEntry(); T instantiateCollection(int size); 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 f38211c..c027ca9 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 @@ -1,8 +1,15 @@ package de.siphalor.tweed5.core.api.entry; import java.util.Map; +import java.util.function.Consumer; public interface CompoundConfigEntry extends ConfigEntry { + @Override + default CompoundConfigEntry apply(Consumer> function) { + ConfigEntry.super.apply(function); + return this; + } + Map> subEntries(); void set(T compoundValue, String key, V value); diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java index 4f333dd..2c4031a 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java @@ -1,18 +1,32 @@ package de.siphalor.tweed5.core.api.entry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; 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; -public interface ConfigEntry { +import java.util.function.Consumer; +import java.util.function.Function; + +public interface ConfigEntry { ConfigContainer container(); Class valueClass(); - EntryExtensionsData extensionsData(); + Patchwork extensionsData(); + + default ConfigEntry apply(Consumer> function) { + function.accept(this); + return this; + } + + default R call(Function, R> function) { + return function.apply(this); + } void visitInOrder(ConfigEntryVisitor visitor); - void visitInOrder(ConfigEntryValueVisitor visitor, T value); + void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value); - T deepCopy(T value); + T deepCopy(@NonNull T value); } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/SimpleConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/SimpleConfigEntry.java index a80772f..63ae619 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/SimpleConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/SimpleConfigEntry.java @@ -1,4 +1,11 @@ package de.siphalor.tweed5.core.api.entry; +import java.util.function.Consumer; + public interface SimpleConfigEntry extends ConfigEntry { + @Override + default SimpleConfigEntry apply(Consumer> function) { + ConfigEntry.super.apply(function); + return this; + } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/EntryExtensionsData.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/EntryExtensionsData.java deleted file mode 100644 index c7df912..0000000 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/EntryExtensionsData.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.siphalor.tweed5.core.api.extension; - -import de.siphalor.tweed5.patchwork.api.Patchwork; - -public interface EntryExtensionsData extends Patchwork { -} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/RegisteredExtensionData.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/RegisteredExtensionData.java deleted file mode 100644 index 3eaefc8..0000000 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/RegisteredExtensionData.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.siphalor.tweed5.core.api.extension; - -import de.siphalor.tweed5.patchwork.api.Patchwork; - -public interface RegisteredExtensionData, E> { - void set(U patchwork, E extension); -} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java index ac73be1..a3aa65c 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java @@ -1,6 +1,8 @@ package de.siphalor.tweed5.core.api.extension; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; + public interface TweedExtensionSetupContext { - RegisteredExtensionData registerEntryExtensionData(Class dataClass); + PatchworkPartAccess registerEntryExtensionData(Class dataClass); void registerExtension(Class extensionClass); } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java index bf24462..1116464 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java @@ -3,21 +3,15 @@ package de.siphalor.tweed5.core.impl; import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.patchwork.api.Patchwork; -import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClass; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart; +import de.siphalor.tweed5.patchwork.api.PatchworkFactory; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; import de.siphalor.tweed5.utils.api.collection.InheritanceMap; import lombok.Getter; -import lombok.Setter; import org.jspecify.annotations.Nullable; -import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; @@ -28,9 +22,7 @@ public class DefaultConfigContainer implements ConfigContainer { private final Set> requestedExtensions = new HashSet<>(); private final InheritanceMap extensions = new InheritanceMap<>(TweedExtension.class); private @Nullable ConfigEntry rootEntry; - private @Nullable PatchworkClass entryExtensionsDataPatchworkClass; - private final Map, RegisteredExtensionDataImpl> registeredEntryDataExtensions - = new HashMap<>(); + private @Nullable PatchworkFactory entryExtensionsDataPatchworkFactory; @Override public void registerExtension(Class extensionClass) { @@ -42,15 +34,12 @@ public class DefaultConfigContainer implements ConfigContainer { public void finishExtensionSetup() { requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP); + PatchworkFactory.Builder entryExtensionDataPatchworkFactoryBuilder = PatchworkFactory.builder(); + TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() { @Override - public RegisteredExtensionData registerEntryExtensionData(Class dataClass) { - if (registeredEntryDataExtensions.containsKey(dataClass)) { - throw new IllegalArgumentException("Extension " + dataClass.getName() + " is already registered"); - } - RegisteredExtensionDataImpl registered = new RegisteredExtensionDataImpl<>(); - registeredEntryDataExtensions.put(dataClass, registered); - return registered; + public PatchworkPartAccess registerEntryExtensionData(Class dataClass) { + return entryExtensionDataPatchworkFactoryBuilder.registerPart(dataClass); } @Override @@ -81,20 +70,7 @@ public class DefaultConfigContainer implements ConfigContainer { } } - PatchworkClassCreator entryExtensionsDataGenerator = PatchworkClassCreator.builder() - .patchworkInterface(EntryExtensionsData.class) - .classPackage("de.siphalor.tweed5.core.generated.entryextensiondata") - .classPrefix("EntryExtensionsData$") - .build(); - try { - entryExtensionsDataPatchworkClass = entryExtensionsDataGenerator.createClass(registeredEntryDataExtensions.keySet()); - for (PatchworkClassPart part : entryExtensionsDataPatchworkClass.parts()) { - RegisteredExtensionDataImpl registeredExtension = registeredEntryDataExtensions.get(part.partInterface()); - registeredExtension.setter(part.fieldSetter()); - } - } catch (PatchworkClassGenerator.GenerationException e) { - throw new IllegalStateException("Failed to create patchwork class for entry extensions' data", e); - } + entryExtensionsDataPatchworkFactory = entryExtensionDataPatchworkFactoryBuilder.build(); setupPhase = ConfigContainerSetupPhase.TREE_SETUP; @@ -230,25 +206,11 @@ public class DefaultConfigContainer implements ConfigContainer { } @Override - public EntryExtensionsData createExtensionsData() { + public Patchwork createExtensionsData() { requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP); - try { - assert entryExtensionsDataPatchworkClass != null; - return (EntryExtensionsData) entryExtensionsDataPatchworkClass.constructor().invoke(); - } catch (Throwable e) { - throw new IllegalStateException("Failed to construct patchwork class for entry extensions' data", e); - } - } - - @Override - public Map, ? extends RegisteredExtensionData> entryDataExtensions() { - requireSetupPhase( - ConfigContainerSetupPhase.TREE_SETUP, - ConfigContainerSetupPhase.TREE_ATTACHED, - ConfigContainerSetupPhase.INITIALIZED - ); - return registeredEntryDataExtensions; + assert entryExtensionsDataPatchworkFactory != null; + return entryExtensionsDataPatchworkFactory.create(); } @Override @@ -295,18 +257,4 @@ public class DefaultConfigContainer implements ConfigContainer { ); } } - - @Setter - private static class RegisteredExtensionDataImpl, E> implements RegisteredExtensionData { - private MethodHandle setter; - - @Override - public void set(U patchwork, E extension) { - try { - setter.invokeWithArguments(patchwork, extension); - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } - } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java index 46c0243..6c7df95 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java @@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.entry.BaseConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor; import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; +import org.jspecify.annotations.NonNull; public class SimpleConfigEntryImpl extends BaseConfigEntry implements SimpleConfigEntry { public SimpleConfigEntryImpl(ConfigContainer container, Class valueClass) { @@ -22,7 +23,7 @@ public class SimpleConfigEntryImpl extends BaseConfigEntry implements Simp } @Override - public T deepCopy(T value) { + public T deepCopy(@NonNull T value) { return value; } } diff --git a/tweed5-core/src/test/java/de/siphalor/tweed5/core/impl/DefaultConfigContainerTest.java b/tweed5-core/src/test/java/de/siphalor/tweed5/core/impl/DefaultConfigContainerTest.java index 5e6bc83..6c2c888 100644 --- a/tweed5-core/src/test/java/de/siphalor/tweed5/core/impl/DefaultConfigContainerTest.java +++ b/tweed5-core/src/test/java/de/siphalor/tweed5/core/impl/DefaultConfigContainerTest.java @@ -2,8 +2,6 @@ package de.siphalor.tweed5.core.impl; import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; @@ -131,28 +129,7 @@ class DefaultConfigContainerTest { configContainer.registerExtension(ExtensionB.class); configContainer.finishExtensionSetup(); var extensionData = configContainer.createExtensionsData(); - assertThat(extensionData).isNotNull() - .satisfies( - data -> assertThat(data.isPatchworkPartDefined(ExtensionBData.class)).isTrue(), - data -> assertThat(data.isPatchworkPartDefined(String.class)).isFalse() - ); - } - - @Test - void entryDataExtensions() { - var configContainer = new DefaultConfigContainer<>(); - configContainer.registerExtension(ExtensionB.class); - configContainer.finishExtensionSetup(); - - assertThat(configContainer.entryDataExtensions()).containsOnlyKeys(ExtensionBData.class); - //noinspection unchecked - var registeredExtension = (RegisteredExtensionData) - configContainer.entryDataExtensions().get(ExtensionBData.class); - - var extensionsData = configContainer.createExtensionsData(); - registeredExtension.set(extensionsData, new ExtensionBDataImpl("blub")); - - assertThat(((ExtensionBData) extensionsData).test()).isEqualTo("blub"); + assertThat(extensionData).isNotNull(); } @SuppressWarnings("unchecked") diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/CommentExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/CommentExtension.java index 2bac99b..8032875 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/CommentExtension.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/CommentExtension.java @@ -5,8 +5,20 @@ import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl; import org.jspecify.annotations.Nullable; +import java.util.function.Consumer; + public interface CommentExtension extends TweedExtension { Class DEFAULT = CommentExtensionImpl.class; + static > Consumer baseComment(String baseComment) { + return entry -> { + CommentExtension extension = entry.container().extension(CommentExtension.class) + .orElseThrow(() -> new IllegalStateException("No comment extension registered")); + extension.setBaseComment(entry, baseComment); + }; + } + + void setBaseComment(ConfigEntry configEntry, String baseComment); + @Nullable String getFullComment(ConfigEntry configEntry); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/EntryComment.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/EntryComment.java deleted file mode 100644 index 73bc53a..0000000 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/api/EntryComment.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.siphalor.tweed5.defaultextensions.comment.api; - -public interface EntryComment { - String comment(); -} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImpl.java index db99ad6..fd1e0be 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImpl.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImpl.java @@ -2,32 +2,32 @@ package de.siphalor.tweed5.defaultextensions.comment.impl; import com.google.auto.service.AutoService; import de.siphalor.tweed5.core.api.container.ConfigContainer; +import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer; import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension; -import de.siphalor.tweed5.defaultextensions.comment.api.*; +import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; +import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension; +import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import lombok.Data; import lombok.Getter; -import lombok.Value; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @AutoService(CommentExtension.class) public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension { private final ConfigContainer configContainer; @Getter - private final RegisteredExtensionData internalEntryDataExtension; + private final PatchworkPartAccess customEntryDataAccess; private final DefaultMiddlewareContainer middlewareContainer; public CommentExtensionImpl(ConfigContainer configContainer, TweedExtensionSetupContext context) { this.configContainer = configContainer; - this.internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class); - context.registerEntryExtensionData(EntryComment.class); + this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class); this.middlewareContainer = new DefaultMiddlewareContainer<>(); } @@ -48,31 +48,49 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE @Override public @Nullable Middleware> entryWriterMiddleware() { - return TweedEntryWriterCommentMiddleware.INSTANCE; + return new TweedEntryWriterCommentMiddleware(this); + } + + @Override + public void setBaseComment(ConfigEntry configEntry, String baseComment) { + if (configEntry.container() != configContainer) { + throw new IllegalArgumentException("config entry doesn't belong to config container of this extension"); + } else if (configContainer.setupPhase().compareTo(ConfigContainerSetupPhase.INITIALIZED) >= 0) { + throw new IllegalStateException("config container must not be initialized"); + } + + getOrCreateCustomEntryData(configEntry).baseComment(baseComment); } @Override public void initEntry(ConfigEntry configEntry) { - EntryExtensionsData entryExtensionsData = configEntry.extensionsData(); - String baseComment; - if (entryExtensionsData.isPatchworkPartSet(EntryComment.class)) { - baseComment = ((EntryComment) entryExtensionsData).comment(); - } else { - baseComment = ""; - } + CustomEntryData entryData = getOrCreateCustomEntryData(configEntry); + entryData.commentProducer(middlewareContainer.process(entry -> entryData.baseComment())); + } - CommentProducer middleware = middlewareContainer.process(entry -> baseComment); - internalEntryDataExtension.set(entryExtensionsData, new InternalCommentEntryDataImpl(middleware)); + private CustomEntryData getOrCreateCustomEntryData(ConfigEntry entry) { + CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess); + if (customEntryData == null) { + customEntryData = new CustomEntryData(); + entry.extensionsData().set(customEntryDataAccess, customEntryData); + } + return customEntryData; } @Override - public @Nullable String getFullComment(@NonNull ConfigEntry configEntry) { - String comment = ((InternalCommentEntryData) configEntry.extensionsData()).commentProducer().createComment(configEntry); + public @Nullable String getFullComment(ConfigEntry configEntry) { + CustomEntryData customEntryData = configEntry.extensionsData().get(customEntryDataAccess); + if (customEntryData == null) { + return null; + } + String comment = customEntryData.commentProducer().createComment(configEntry); return comment.isEmpty() ? null : comment; } - @Value - private static class InternalCommentEntryDataImpl implements InternalCommentEntryData { - CommentProducer commentProducer; + + @Data + private static class CustomEntryData { + private String baseComment = ""; + private CommentProducer commentProducer; } } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/InternalCommentEntryData.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/InternalCommentEntryData.java deleted file mode 100644 index c825543..0000000 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/InternalCommentEntryData.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.siphalor.tweed5.defaultextensions.comment.impl; - -import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer; - -public interface InternalCommentEntryData { - CommentProducer commentProducer(); -} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java index 2bce6cc..afe6716 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java @@ -5,12 +5,14 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; +import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +@RequiredArgsConstructor class TweedEntryWriterCommentMiddleware implements Middleware> { - public static final TweedEntryWriterCommentMiddleware INSTANCE = new TweedEntryWriterCommentMiddleware(); + private final CommentExtension commentExtension; @Override public String id() { @@ -49,7 +51,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware compoundConfigEntry; @@ -134,11 +136,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware entry) { - if (!entry.extensionsData().isPatchworkPartSet(InternalCommentEntryData.class)) { - return null; - } - String comment = ((InternalCommentEntryData) entry.extensionsData()).commentProducer().createComment(entry).trim(); - return comment.isEmpty() ? null : comment; + private @Nullable String getEntryComment(ConfigEntry entry) { + return commentExtension.getFullComment(entry); } } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java index 318a3ad..40b1410 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java @@ -5,7 +5,7 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayDeque; import java.util.Deque; -public class PathTracking implements PatherData { +public class PathTracking { private final StringBuilder pathBuilder = new StringBuilder(256); private final Deque contextStack = new ArrayDeque<>(50); private final Deque pathParts = new ArrayDeque<>(50); @@ -52,8 +52,7 @@ public class PathTracking implements PatherData { return index; } - @Override - public String valuePath() { + public String currentPath() { return pathBuilder.toString(); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java index e27b2f0..c74b22d 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java @@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.pather.api; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; @RequiredArgsConstructor public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor { @@ -10,7 +11,7 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi private final PathTracking pathTracking; @Override - public void visitEntry(ConfigEntry entry, T value) { + public void visitEntry(ConfigEntry entry, T value) { delegate.visitEntry(entry, value); entryVisited(); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherData.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherData.java deleted file mode 100644 index 4109113..0000000 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherData.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.siphalor.tweed5.defaultextensions.pather.api; - -/** - * Extension data for {@link de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData} - * that provides the path to the value currently being read/written. - */ -public interface PatherData { - String valuePath(); -} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherExtension.java index 59f871f..c523f21 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherExtension.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherExtension.java @@ -1,8 +1,13 @@ package de.siphalor.tweed5.defaultextensions.pather.api; import de.siphalor.tweed5.core.api.extension.TweedExtension; +import de.siphalor.tweed5.data.extension.api.TweedReadContext; +import de.siphalor.tweed5.data.extension.api.TweedWriteContext; import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl; public interface PatherExtension extends TweedExtension { Class DEFAULT = PatherExtensionImpl.class; + + String getPath(TweedReadContext context); + String getPath(TweedWriteContext context); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java index 8765528..4a30b3b 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java @@ -2,29 +2,33 @@ package de.siphalor.tweed5.defaultextensions.pather.impl; import com.google.auto.service.AutoService; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.middleware.Middleware; -import de.siphalor.tweed5.data.extension.api.*; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; +import de.siphalor.tweed5.data.extension.api.TweedEntryReader; +import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; +import de.siphalor.tweed5.data.extension.api.TweedReadContext; +import de.siphalor.tweed5.data.extension.api.TweedWriteContext; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; -import de.siphalor.tweed5.defaultextensions.pather.api.*; +import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking; +import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataReader; +import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataVisitor; +import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; import lombok.val; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; @AutoService(PatherExtension.class) -@NullUnmarked public class PatherExtensionImpl implements PatherExtension, TweedExtension, ReadWriteRelatedExtension { private static final String PATHER_ID = "pather"; - private RegisteredExtensionData rwContextPathTrackingData; - private Middleware> entryReaderMiddleware; - private Middleware> entryWriterMiddleware; + private final Middleware> entryReaderMiddleware = createEntryReaderMiddleware(); + private final Middleware> entryWriterMiddleware = createEntryWriterMiddleware(); + + private @Nullable PatchworkPartAccess rwContextPathTrackingAccess; @Override public String getId() { @@ -33,13 +37,32 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea @Override public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) { - rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PatherData.class); - - entryReaderMiddleware = createEntryReaderMiddleware(); - entryWriterMiddleware = createEntryWriterMiddleware(); + rwContextPathTrackingAccess = context.registerReadWriteContextExtensionData(PathTracking.class); } - private @NonNull Middleware> createEntryReaderMiddleware() { + @Override + public String getPath(TweedReadContext context) { + assert rwContextPathTrackingAccess != null; + + PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess); + if (pathTracking == null) { + throw new IllegalStateException("Path tracking is not active!"); + } + return pathTracking.currentPath(); + } + + @Override + public String getPath(TweedWriteContext context) { + assert rwContextPathTrackingAccess != null; + + PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess); + if (pathTracking == null) { + throw new IllegalStateException("Path tracking is not active!"); + } + return pathTracking.currentPath(); + } + + private Middleware> createEntryReaderMiddleware() { return new Middleware>() { @Override public String id() { @@ -48,16 +71,19 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea @Override public TweedEntryReader process(TweedEntryReader inner) { + assert rwContextPathTrackingAccess != null; + //noinspection unchecked val castedInner = (TweedEntryReader>) inner; return (TweedDataReader reader, ConfigEntry entry, TweedReadContext context) -> { - if (context.extensionsData().isPatchworkPartSet(PatherData.class)) { + PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess); + if (pathTracking != null) { return castedInner.read(reader, entry, context); } - PathTracking pathTracking = new PathTracking(); - rwContextPathTrackingData.set(context.extensionsData(), pathTracking); + pathTracking = new PathTracking(); + context.extensionsData().set(rwContextPathTrackingAccess, pathTracking); return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context); }; } @@ -73,17 +99,20 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea @Override public TweedEntryWriter process(TweedEntryWriter inner) { + assert rwContextPathTrackingAccess != null; + //noinspection unchecked val castedInner = (TweedEntryWriter>) inner; return (TweedDataVisitor writer, Object value, ConfigEntry entry, TweedWriteContext context) -> { - if (context.extensionsData().isPatchworkPartSet(PatherData.class)) { + PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess); + if (pathTracking != null) { castedInner.write(writer, value, entry, context); return; } - PathTracking pathTracking = new PathTracking(); - rwContextPathTrackingData.set(context.extensionsData(), pathTracking); + pathTracking = new PathTracking(); + context.extensionsData().set(rwContextPathTrackingAccess, pathTracking); castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context); }; } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/EntrySpecificValidation.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/EntrySpecificValidation.java deleted file mode 100644 index 96d3972..0000000 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/EntrySpecificValidation.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.siphalor.tweed5.defaultextensions.validation.api; - -import de.siphalor.tweed5.core.api.middleware.Middleware; - -import java.util.Collection; - -public interface EntrySpecificValidation { - Collection> validators(); -} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java index df80db3..af241bb 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java @@ -2,12 +2,54 @@ package de.siphalor.tweed5.defaultextensions.validation.api; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.extension.TweedExtension; +import de.siphalor.tweed5.core.api.middleware.Middleware; +import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues; +import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware; import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; + public interface ValidationExtension extends TweedExtension { Class DEFAULT = ValidationExtensionImpl.class; + static , T> Consumer validators(ConfigEntryValidator... validators) { + return entry -> { + ValidationExtension extension = entry.container().extension(ValidationExtension.class) + .orElseThrow(() -> new IllegalStateException("No validation extension registered")); + extension.addValidators(entry, validators); + }; + } + + static , T> Function validate(T value) { + return entry -> { + ValidationExtension extension = entry.container().extension(ValidationExtension.class) + .orElseThrow(() -> new IllegalStateException("No validation extension registered")); + return extension.validate(entry, value); + }; + } + + default void addValidators(ConfigEntry entry, ConfigEntryValidator... validators) { + String lastId = null; + for (ConfigEntryValidator validator : validators) { + String id = UUID.randomUUID().toString(); + Set mustComeAfter = lastId == null ? Collections.emptySet() : Collections.singleton(lastId); + + addValidatorMiddleware(entry, new SimpleValidatorMiddleware(id, validator) { + @Override + public Set mustComeAfter() { + return mustComeAfter; + } + }); + + lastId = id; + } + } + void addValidatorMiddleware(ConfigEntry entry, Middleware validator); + ValidationIssues validate(ConfigEntry entry, T value); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java index f777e5a..9611a5f 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java @@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.validation.api.result; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -11,16 +12,16 @@ import java.util.function.Function; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public class ValidationResult { +public class ValidationResult { private final T value; private final Collection issues; private final boolean hasError; - public static ValidationResult ok(T value) { + public static ValidationResult ok(T value) { return new ValidationResult<>(value, Collections.emptyList(), false); } - public static ValidationResult withIssues(T value, Collection issues) { + public static ValidationResult withIssues(T value, Collection issues) { return new ValidationResult<>(value, issues, issuesContainError(issues)); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/InternalValidationEntryData.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/InternalValidationEntryData.java deleted file mode 100644 index 3caadf9..0000000 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/InternalValidationEntryData.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.siphalor.tweed5.defaultextensions.validation.impl; - -import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator; - -public interface InternalValidationEntryData { - ConfigEntryValidator completeEntryValidator(); -} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java index 8bb7081..a575496 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java @@ -4,8 +4,6 @@ import com.google.auto.service.AutoService; import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer; @@ -14,7 +12,6 @@ import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer; import de.siphalor.tweed5.data.extension.api.TweedEntryReadException; import de.siphalor.tweed5.data.extension.api.TweedEntryReader; import de.siphalor.tweed5.data.extension.api.TweedReadContext; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.dataapi.api.TweedDataReader; @@ -22,20 +19,16 @@ import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtensio import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer; import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking; import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor; -import de.siphalor.tweed5.defaultextensions.pather.api.PatherData; import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator; -import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Value; -import org.jetbrains.annotations.NotNull; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import lombok.*; import org.jspecify.annotations.Nullable; import java.util.*; @@ -48,7 +41,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid ); private static final ConfigEntryValidator PRIMITIVE_VALIDATOR = new ConfigEntryValidator() { @Override - public ValidationResult validate(@NotNull ConfigEntry configEntry, @Nullable T value) { + public ValidationResult validate(ConfigEntry configEntry, @Nullable T value) { if (value == null) { //noinspection unchecked return (ValidationResult) PRIMITIVE_IS_NULL_RESULT; @@ -57,33 +50,32 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid } @Override - public String description(@NotNull ConfigEntry configEntry) { + public String description(ConfigEntry configEntry) { return "Value must not be null."; } }; private static final ConfigEntryValidator NOOP_VALIDATOR = new ConfigEntryValidator() { @Override - public ValidationResult validate(@NotNull ConfigEntry configEntry, @Nullable T value) { + public ValidationResult<@Nullable T> validate(ConfigEntry configEntry, @Nullable T value) { return ValidationResult.ok(value); } @Override - public String description(@NotNull ConfigEntry configEntry) { + public String description(ConfigEntry configEntry) { return ""; } }; private final ConfigContainer configContainer; - private final RegisteredExtensionData validationEntryDataExtension; + private final PatchworkPartAccess customEntryDataAccess; private final MiddlewareContainer entryValidatorMiddlewareContainer = new DefaultMiddlewareContainer<>(); - private @Nullable RegisteredExtensionData - readContextValidationIssuesExtensionData; + private @Nullable PatchworkPartAccess readContextValidationIssuesAccess; + private @Nullable PatherExtension patherExtension; public ValidationExtensionImpl(ConfigContainer configContainer, TweedExtensionSetupContext context) { this.configContainer = configContainer; - this.validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class); - context.registerEntryExtensionData(EntrySpecificValidation.class); + this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class); context.registerExtension(PatherExtension.class); } @@ -102,6 +94,9 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid } } entryValidatorMiddlewareContainer.seal(); + + patherExtension = configContainer.extension(PatherExtension.class) + .orElseThrow(() -> new IllegalStateException("Missing requested PatherExtension")); } @Override @@ -116,16 +111,21 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid public CommentProducer process(CommentProducer inner) { return entry -> { String baseComment = inner.createComment(entry); - if (entry.extensionsData().isPatchworkPartSet(InternalValidationEntryData.class)) { - String validationDescription = ((InternalValidationEntryData) entry.extensionsData()) - .completeEntryValidator() - .description(entry) - .trim(); - if (!validationDescription.isEmpty()) { - baseComment += "\n\n" + validationDescription; - } + CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess); + if (entryData == null || entryData.completeValidator() == null) { + return baseComment; + } + String validationDescription = entryData.completeValidator() + .description(entry) + .trim(); + if (validationDescription.isEmpty()) { + return baseComment; + } + if (baseComment.isEmpty()) { + return validationDescription; + } else { + return "\n\n" + validationDescription; } - return baseComment; }; } }; @@ -133,7 +133,13 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid @Override public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) { - readContextValidationIssuesExtensionData = context.registerReadWriteContextExtensionData(ValidationIssues.class); + readContextValidationIssuesAccess = context.registerReadWriteContextExtensionData(ValidationIssues.class); + } + + @Override + public void addValidatorMiddleware(ConfigEntry entry, Middleware validator) { + CustomEntryData entryData = getOrCreateCustomEntryData(entry); + entryData.addValidator(validator); } @Override @@ -145,26 +151,26 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid baseValidator = NOOP_VALIDATOR; } - ConfigEntryValidator entryValidator; - Collection> entrySpecificValidators = getEntrySpecificValidators(configEntry); - if (entrySpecificValidators.isEmpty()) { - entryValidator = entryValidatorMiddlewareContainer.process(baseValidator); + CustomEntryData entryData = getOrCreateCustomEntryData(configEntry); + + if (entryData.validators().isEmpty()) { + entryData.completeValidator(entryValidatorMiddlewareContainer.process(baseValidator)); } else { DefaultMiddlewareContainer entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>(); entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares()); - entrySpecificValidatorContainer.registerAll(entrySpecificValidators); + entrySpecificValidatorContainer.registerAll(entryData.validators()); entrySpecificValidatorContainer.seal(); - entryValidator = entrySpecificValidatorContainer.process(baseValidator); + entryData.completeValidator(entrySpecificValidatorContainer.process(baseValidator)); } - - validationEntryDataExtension.set(configEntry.extensionsData(), new InternalValidationEntryDataImpl(entryValidator)); } - private Collection> getEntrySpecificValidators(ConfigEntry configEntry) { - if (!configEntry.extensionsData().isPatchworkPartSet(EntrySpecificValidation.class)) { - return Collections.emptyList(); + private CustomEntryData getOrCreateCustomEntryData(ConfigEntry entry) { + CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess); + if (entryData == null) { + entryData = new CustomEntryData(); + entry.extensionsData().set(customEntryDataAccess, entryData); } - return ((EntrySpecificValidation) configEntry.extensionsData()).validators(); + return entryData; } @Override @@ -173,7 +179,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid } @Override - public ValidationIssues validate(@NotNull ConfigEntry entry, @Nullable T value) { + public ValidationIssues validate(ConfigEntry entry, @Nullable T value) { PathTracking pathTracking = new PathTracking(); ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking); @@ -182,9 +188,22 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid return validatingVisitor.validationIssues(); } - @Value - private static class InternalValidationEntryDataImpl implements InternalValidationEntryData { - ConfigEntryValidator completeEntryValidator; + @Data + private static class CustomEntryData { + @Setter(AccessLevel.NONE) + private @Nullable List> validators; + private @Nullable ConfigEntryValidator completeValidator; + + public List> validators() { + return validators == null ? Collections.emptyList() : validators; + } + + public void addValidator(Middleware validator) { + if (validators == null) { + validators = new ArrayList<>(); + } + validators.add(validator); + } } private class EntryValidationReaderMiddleware implements Middleware> { @@ -200,23 +219,29 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid @Override public TweedEntryReader process(TweedEntryReader inner) { + assert readContextValidationIssuesAccess != null && patherExtension != null; + //noinspection unchecked TweedEntryReader> castedInner = (TweedEntryReader>) inner; return (TweedDataReader reader, ConfigEntry entry, TweedReadContext context) -> { - ValidationIssues validationIssues; - if (!context.extensionsData().isPatchworkPartSet(ValidationIssues.class)) { + ValidationIssues validationIssues = context.extensionsData().get(readContextValidationIssuesAccess); + if (validationIssues == null) { validationIssues = new ValidationIssuesImpl(); - readContextValidationIssuesExtensionData.set(context.extensionsData(), validationIssues); } else { validationIssues = (ValidationIssues) context.extensionsData(); } Object value = castedInner.read(reader, entry, context); - ValidationResult validationResult = ((InternalValidationEntryData) entry.extensionsData()).completeEntryValidator().validate(entry, value); + ConfigEntryValidator entryValidator = entry.extensionsData() + .get(customEntryDataAccess) + .completeValidator(); + assert entryValidator != null; - if (!validationResult.issues().isEmpty() && context.extensionsData().isPatchworkPartSet(PatherData.class)) { - String path = ((PatherData) context.extensionsData()).valuePath(); + ValidationResult validationResult = entryValidator.validate(entry, value); + + if (!validationResult.issues().isEmpty()) { + String path = patherExtension.getPath(context); validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues( entry, validationResult.issues() @@ -234,15 +259,19 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid @Getter @RequiredArgsConstructor - private static class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor { + private class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor { private final PathTracking pathTracking; private final ValidationIssues validationIssues = new ValidationIssuesImpl(); @Override public void visitEntry(ConfigEntry entry, T value) { - ValidationResult result = ((InternalValidationEntryData) entry.extensionsData()).completeEntryValidator().validate(entry, value); + CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess); + assert entryData != null; + ConfigEntryValidator entryValidator = entryData.completeValidator(); + assert entryValidator != null; + ValidationResult result = entryValidator.validate(entry, value); if (!result.issues().isEmpty()) { - validationIssues.issuesByPath().put(pathTracking.valuePath(), new ValidationIssues.EntryIssues(entry, result.issues())); + validationIssues.issuesByPath().put(pathTracking.currentPath(), new ValidationIssues.EntryIssues(entry, result.issues())); } } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java index be1efc8..b0553f0 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackExtension.java @@ -1,8 +1,21 @@ package de.siphalor.tweed5.defaultextensions.validationfallback.api; +import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl; +import java.util.function.Consumer; + public interface ValidationFallbackExtension extends TweedExtension { Class DEFAULT = ValidationFallbackExtensionImpl.class; + + static , T> Consumer validationFallbackValue(T value) { + return entry -> { + ValidationFallbackExtension extension = entry.container().extension(ValidationFallbackExtension.class) + .orElseThrow(() -> new IllegalStateException("ValidationFallbackExtension is not registered")); + extension.setFallbackValue(entry, value); + }; + } + + void setFallbackValue(ConfigEntry entry, T value); } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackValue.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackValue.java deleted file mode 100644 index c6dddfc..0000000 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/api/ValidationFallbackValue.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.siphalor.tweed5.defaultextensions.validationfallback.api; - -import org.jspecify.annotations.Nullable; - -public interface ValidationFallbackValue { - @Nullable Object validationFallbackValue(); -} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java index 1992cce..5f515ca 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImpl.java @@ -10,7 +10,8 @@ import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssu import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult; import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension; -import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import lombok.Data; import org.jspecify.annotations.Nullable; import java.util.ArrayList; @@ -20,8 +21,11 @@ import java.util.stream.Collectors; @AutoService(ValidationFallbackExtension.class) public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension { + + private final PatchworkPartAccess customEntryDataAccess; + public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) { - context.registerEntryExtensionData(ValidationFallbackValue.class); + customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class); } @Override @@ -29,12 +33,31 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens return "validation-fallback"; } + @Override + public void setFallbackValue(ConfigEntry entry, T value) { + getOrCreateCustomEntryData(entry).fallbackValue(value); + } + + private CustomEntryData getOrCreateCustomEntryData(ConfigEntry entry) { + CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess); + if (customEntryData == null) { + customEntryData = new CustomEntryData(); + entry.extensionsData().set(customEntryDataAccess, customEntryData); + } + return customEntryData; + } + @Override public Middleware validationMiddleware() { return new ValidationFallbackMiddleware(); } - private static class ValidationFallbackMiddleware implements Middleware { + @Data + private static class CustomEntryData { + @Nullable Object fallbackValue; + } + + private class ValidationFallbackMiddleware implements Middleware { @Override public String id() { return "validation-fallback"; @@ -59,13 +82,14 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens if (!result.hasError()) { return result; } - if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) { + CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess); + if (entryData == null) { return result; } - Object fallbackValue = ((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue(); + Object fallbackValue = entryData.fallbackValue(); if (fallbackValue != null) { - if (fallbackValue.getClass() == configEntry.valueClass()) { + if (configEntry.valueClass().isInstance(fallbackValue)) { //noinspection unchecked fallbackValue = configEntry.deepCopy((T) fallbackValue); } else { @@ -90,12 +114,11 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens @Override public String description(ConfigEntry configEntry) { - if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) { + CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess); + if (entryData == null) { return inner.description(configEntry); } - return inner.description(configEntry) + - "\n\nDefault/Fallback value: " + - ((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue(); + return inner.description(configEntry) + "\n\nDefault/Fallback value: " + entryData.fallbackValue(); } }; } diff --git a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImplTest.java b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImplTest.java index bb9d4d6..8705e76 100644 --- a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImplTest.java +++ b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/comment/impl/CommentExtensionImplTest.java @@ -1,27 +1,20 @@ package de.siphalor.tweed5.defaultextensions.comment.impl; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; +import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; +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.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; -import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition; 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.readwrite.TweedEntryReaderWriter; -import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters; import de.siphalor.tweed5.data.hjson.HjsonCommentType; import de.siphalor.tweed5.data.hjson.HjsonWriter; import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension; import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer; -import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.Value; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Test; @@ -31,6 +24,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter; +import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*; +import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment; import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap; import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.*; @@ -38,44 +34,44 @@ import static org.junit.jupiter.api.Assertions.*; @NullUnmarked class CommentExtensionImplTest { - private DefaultConfigContainer> configContainer; - private StaticMapCompoundConfigEntryImpl> rootEntry; - private SimpleConfigEntryImpl intEntry; - private SimpleConfigEntryImpl stringEntry; - private SimpleConfigEntryImpl noCommentEntry; + private DefaultConfigContainer<@NonNull Map> configContainer; + private CompoundConfigEntry> rootEntry; + private SimpleConfigEntry intEntry; + private SimpleConfigEntry stringEntry; + private SimpleConfigEntry noCommentEntry; @SafeVarargs final void setupContainer(Class... extraExtensions) { configContainer = new DefaultConfigContainer<>(); configContainer.registerExtension(CommentExtension.DEFAULT); + configContainer.registerExtension(ReadWriteExtension.DEFAULT); configContainer.registerExtensions(extraExtensions); configContainer.finishExtensionSetup(); - intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class); - stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class); - noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class); + intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class) + .apply(entryReaderWriter(intReaderWriter())) + .apply(baseComment("It is an integer")); + stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class) + .apply(entryReaderWriter(stringReaderWriter())) + .apply(baseComment("It is a string")); + noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class) + .apply(entryReaderWriter(longReaderWriter())); //noinspection unchecked rootEntry = new StaticMapCompoundConfigEntryImpl<>( configContainer, - ((Class>)(Class) Map.class), + ((Class>) (Class) Map.class), LinkedHashMap::new, sequencedMap(List.of( entry("int", intEntry), entry("string", stringEntry), entry("noComment", noCommentEntry) - )) - ); + ))) + .apply(entryReaderWriter(compoundReaderWriter())) + .apply(baseComment("This is the root value.\nIt is the topmost value in the tree.")); configContainer.attachTree(rootEntry); - - //noinspection unchecked - RegisteredExtensionData commentData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryComment.class); - - commentData.set(rootEntry.extensionsData(), new CommentImpl("This is the root value.\nIt is the topmost value in the tree.")); - commentData.set(intEntry.extensionsData(), new CommentImpl("It is an integer")); - commentData.set(stringEntry.extensionsData(), new CommentImpl("It is a string")); } @Test @@ -102,8 +98,7 @@ class CommentExtensionImplTest { @Test void simpleCommentsInHjson() { - setupContainer(ReadWriteExtension.DEFAULT); - setupReadWriteTypes(); + setupContainer(); configContainer.initialize(); Map value = new HashMap<>(); @@ -134,21 +129,6 @@ class CommentExtensionImplTest { """, output.toString()); } - private void setupReadWriteTypes() { - //noinspection unchecked - var readerWriterData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class); - - readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter())); - readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter())); - readerWriterData.set(stringEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.stringReaderWriter())); - readerWriterData.set(noCommentEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.longReaderWriter())); - } - - @Value - private static class CommentImpl implements EntryComment { - String comment; - } - @NoArgsConstructor public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension { @Override @@ -158,7 +138,7 @@ class CommentExtensionImplTest { @Override public Middleware commentMiddleware() { - return new Middleware() { + return new Middleware<>() { @Override public String id() { return "test"; @@ -171,19 +151,4 @@ class CommentExtensionImplTest { }; } } - - @RequiredArgsConstructor - private static class TrivialEntryReaderWriterDefinition implements EntryReaderWriterDefinition { - private final TweedEntryReaderWriter readerWriter; - - @Override - public TweedEntryReader reader() { - return readerWriter; - } - - @Override - public TweedEntryWriter writer() { - return readerWriter; - } - } } diff --git a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java index 304ae50..7669c0b 100644 --- a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java +++ b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java @@ -1,37 +1,39 @@ package de.siphalor.tweed5.defaultextensions.validation.impl; +import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; +import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; import de.siphalor.tweed5.core.impl.DefaultConfigContainer; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; -import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment; -import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues; import de.siphalor.tweed5.defaultextensions.validation.api.validators.NumberRangeValidator; -import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment; +import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators; import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap; import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.*; class ValidationExtensionImplTest { private DefaultConfigContainer> configContainer; - private StaticMapCompoundConfigEntryImpl> rootEntry; - private SimpleConfigEntryImpl byteEntry; - private SimpleConfigEntryImpl intEntry; - private SimpleConfigEntryImpl doubleEntry; + private CompoundConfigEntry> rootEntry; + private SimpleConfigEntry byteEntry; + private SimpleConfigEntry intEntry; + private SimpleConfigEntry doubleEntry; @BeforeEach void setUp() { @@ -41,9 +43,13 @@ class ValidationExtensionImplTest { configContainer.registerExtension(ValidationExtension.DEFAULT); configContainer.finishExtensionSetup(); - byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class); - intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class); - doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class); + byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class) + .apply(validators(new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100))); + intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class) + .apply(validators(new NumberRangeValidator<>(Integer.class, null, 123))) + .apply(baseComment("This is the main comment!")); + doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class) + .apply(validators(new NumberRangeValidator<>(Double.class, 0.5, null))); //noinspection unchecked rootEntry = new StaticMapCompoundConfigEntryImpl<>( @@ -59,25 +65,6 @@ class ValidationExtensionImplTest { configContainer.attachTree(rootEntry); - - //noinspection unchecked - RegisteredExtensionData commentData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryComment.class); - commentData.set(intEntry.extensionsData(), () -> "This is the main comment!"); - //noinspection unchecked - RegisteredExtensionData entrySpecificValidation = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntrySpecificValidation.class); - entrySpecificValidation.set( - byteEntry.extensionsData(), - () -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100))) - ); - entrySpecificValidation.set( - intEntry.extensionsData(), - () -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Integer.class, null, 123))) - ); - entrySpecificValidation.set( - doubleEntry.extensionsData(), - () -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Double.class, 0.5, null))) - ); - configContainer.initialize(); } diff --git a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java index c2e1234..68d8ae1 100644 --- a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java +++ b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validationfallback/impl/ValidationFallbackExtensionImplTest.java @@ -1,30 +1,21 @@ package de.siphalor.tweed5.defaultextensions.validationfallback.impl; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; +import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; import de.siphalor.tweed5.core.impl.DefaultConfigContainer; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; -import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition; 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.readwrite.TweedEntryReaderWriters; import de.siphalor.tweed5.data.hjson.HjsonCommentType; import de.siphalor.tweed5.data.hjson.HjsonLexer; import de.siphalor.tweed5.data.hjson.HjsonReader; import de.siphalor.tweed5.data.hjson.HjsonWriter; import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator; -import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult; -import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware; import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension; -import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue; -import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,18 +24,19 @@ import org.junit.jupiter.params.provider.ValueSource; import java.io.StringReader; import java.io.StringWriter; -import java.util.Arrays; import java.util.Collections; -import java.util.Set; +import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*; +import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*; +import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators; +import static de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension.validationFallbackValue; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; class ValidationFallbackExtensionImplTest { private DefaultConfigContainer configContainer; - private SimpleConfigEntryImpl intEntry; + private SimpleConfigEntry intEntry; - @SuppressWarnings("unchecked") @BeforeEach void setUp() { configContainer = new DefaultConfigContainer<>(); @@ -55,92 +47,82 @@ class ValidationFallbackExtensionImplTest { configContainer.finishExtensionSetup(); - intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class); + intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class) + .apply(entryReaderWriter(nullableReader(intReaderWriter()), nullableWriter(intReaderWriter()))) + .apply(validators( + new ConfigEntryValidator() { + @Override + public ValidationResult validate(ConfigEntry configEntry, @Nullable T value) { + if (value == null) { + return ValidationResult.withIssues( + null, Collections.singleton( + new ValidationIssue( + "Value must not be null", + ValidationIssueLevel.ERROR + ) + ) + ); + } + return ValidationResult.ok(value); + } + + @Override + public String description(ConfigEntry configEntry) { + return "Must not be null."; + } + }, + new ConfigEntryValidator() { + + @Override + public ValidationResult validate(ConfigEntry configEntry, @Nullable T value) { + assert value != null; + int intValue = (int) value; + if (intValue < 1) { + return ValidationResult.withIssues( + value, + Collections.singleton(new ValidationIssue( + "Must be greater or equal to 1", + ValidationIssueLevel.ERROR + )) + ); + } + if (intValue > 6) { + return ValidationResult.withIssues( + value, + Collections.singleton(new ValidationIssue( + "Must be smaller or equal to 6", + ValidationIssueLevel.ERROR + )) + ); + } + return ValidationResult.ok(value); + } + + @Override + public String description(ConfigEntry configEntry) { + return "Must be between 1 and 6"; + } + } + )) + .apply(validationFallbackValue(3)); + configContainer.attachTree(intEntry); - - RegisteredExtensionData entrySpecificValidation = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntrySpecificValidation.class); - entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList( - new SimpleValidatorMiddleware("non-null", new ConfigEntryValidator() { - @Override - public ValidationResult validate(ConfigEntry configEntry, @Nullable T value) { - if (value == null) { - return ValidationResult.withIssues(null, Collections.singleton( - new ValidationIssue("Value must not be null", ValidationIssueLevel.ERROR) - )); - } - return ValidationResult.ok(value); - } - - @Override - public @NotNull String description(ConfigEntry configEntry) { - return "Must not be null."; - } - }), - new SimpleValidatorMiddleware("range", new ConfigEntryValidator() { - @Override - public ValidationResult validate(ConfigEntry configEntry, @Nullable T value) { - Integer intValue = (Integer) value; - if (intValue < 1) { - return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be greater or equal to 1", ValidationIssueLevel.ERROR))); - } - if (intValue > 6) { - return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be smaller or equal to 6", ValidationIssueLevel.ERROR))); - } - return ValidationResult.ok(value); - } - - @Override - public @NotNull String description(ConfigEntry configEntry) { - return "Must be between 1 and 6"; - } - }) { - @Override - public Set mustComeAfter() { - return Collections.singleton("non-null"); - } - } - )); - - RegisteredExtensionData validationFallbackValue = (RegisteredExtensionData) configContainer.entryDataExtensions().get(ValidationFallbackValue.class); - validationFallbackValue.set(intEntry.extensionsData(), () -> 3); - - RegisteredExtensionData readerWriterData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class); - readerWriterData.set(intEntry.extensionsData(), new EntryReaderWriterDefinition() { - @Override - public TweedEntryReader reader() { - return TweedEntryReaderWriters.nullableReader(TweedEntryReaderWriters.intReaderWriter()); - } - - @Override - public TweedEntryWriter writer() { - return TweedEntryReaderWriters.nullableWriter(TweedEntryReaderWriters.intReaderWriter()); - } - }); - configContainer.initialize(); } @ParameterizedTest @ValueSource(strings = {"0", "7", "123", "null"}) void fallbackTriggers(String input) { - ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); - Integer result = assertDoesNotThrow(() -> readWriteExtension.read( - new HjsonReader(new HjsonLexer(new StringReader(input))), - intEntry, - readWriteExtension.createReadWriteContextExtensionsData() - )); + Integer result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input))))); assertEquals(3, result); } @Test void description() { - ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); StringWriter stringWriter = new StringWriter(); - assertDoesNotThrow(() -> readWriteExtension.write( + intEntry.apply(write( new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)), - 5, - intEntry, - readWriteExtension.createReadWriteContextExtensionsData() + 5 )); assertEquals( diff --git a/tweed5-patchwork/build.gradle.kts b/tweed5-patchwork/build.gradle.kts index f17c1a0..5bc3d71 100644 --- a/tweed5-patchwork/build.gradle.kts +++ b/tweed5-patchwork/build.gradle.kts @@ -1,8 +1,3 @@ plugins { id("de.siphalor.tweed5.base-module") } - -dependencies { - implementation(libs.asm.core) - implementation(libs.asm.commons) -} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/InvalidPatchworkAccessException.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/InvalidPatchworkAccessException.java new file mode 100644 index 0000000..279c256 --- /dev/null +++ b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/InvalidPatchworkAccessException.java @@ -0,0 +1,7 @@ +package de.siphalor.tweed5.patchwork.api; + +public class InvalidPatchworkAccessException extends RuntimeException { + public InvalidPatchworkAccessException(String message) { + super(message); + } +} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/Patchwork.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/Patchwork.java index 6848763..55c2b30 100644 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/Patchwork.java +++ b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/Patchwork.java @@ -1,8 +1,16 @@ package de.siphalor.tweed5.patchwork.api; -public interface Patchwork> { - boolean isPatchworkPartDefined(Class patchworkInterface); - boolean isPatchworkPartSet(Class patchworkInterface); +import org.jetbrains.annotations.CheckReturnValue; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; - S copy(); +public interface Patchwork { + @Contract(pure = true) + @Nullable T get(PatchworkPartAccess access) throws InvalidPatchworkAccessException; + @Contract(mutates = "this") + void set(PatchworkPartAccess access, T value) throws InvalidPatchworkAccessException; + + @CheckReturnValue + @Contract(pure = true) + Patchwork copy(); } diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkClassCreator.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkClassCreator.java deleted file mode 100644 index 7dc22b4..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkClassCreator.java +++ /dev/null @@ -1,90 +0,0 @@ -package de.siphalor.tweed5.patchwork.api; - -import de.siphalor.tweed5.patchwork.impl.ByteArrayClassLoader; -import de.siphalor.tweed5.patchwork.impl.PatchworkClass; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.Value; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@Value -public class PatchworkClassCreator

> { - Class

patchworkInterface; - PatchworkClassGenerator.Config generatorConfig; - - public static

> Builder

builder() { - return new Builder<>(); - } - - public PatchworkClass

createClass(Collection> partInterfaces) throws - PatchworkClassGenerator.GenerationException { - List parts = partInterfaces.stream() - .map(PatchworkClassPart::new) - .collect(Collectors.toList()); - - PatchworkClassGenerator generator = new PatchworkClassGenerator(generatorConfig, parts); - try { - generator.verify(); - } catch (PatchworkClassGenerator.VerificationException e) { - throw new IllegalArgumentException(e); - } - generator.generate(); - byte[] classBytes = generator.emit(); - //noinspection unchecked - Class

patchworkClass = (Class

) ByteArrayClassLoader.loadClass(null, classBytes); - - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - for (PatchworkClassPart part : parts) { - try { - MethodHandle setterHandle = lookup.findSetter(patchworkClass, part.fieldName(), part.partInterface()); - part.fieldSetter(setterHandle); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException( - "Failed to access setter for patchwork part " + part.partInterface().getName(), - e - ); - } - } - try { - MethodHandle constructorHandle = lookup.findConstructor(patchworkClass, MethodType.methodType(Void.TYPE)); - - return PatchworkClass.

builder() - .classPackage(generatorConfig.classPackage()) - .className(generator.className()) - .theClass(patchworkClass) - .constructor(constructorHandle) - .parts(parts) - .build(); - - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Failed to access constructor of patchwork class", e); - } - } - - @Setter - @NoArgsConstructor(access = AccessLevel.PRIVATE) - public static class Builder

> { - private Class

patchworkInterface; - private String classPackage; - private String classPrefix = ""; - - public PatchworkClassCreator

build() { - return new PatchworkClassCreator<>( - patchworkInterface, - new PatchworkClassGenerator.Config(classPackage) - .classPrefix(classPrefix) - .markerInterfaces(Collections.singletonList(patchworkInterface)) - ); - } - } -} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkFactory.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkFactory.java new file mode 100644 index 0000000..fbbcd99 --- /dev/null +++ b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkFactory.java @@ -0,0 +1,16 @@ +package de.siphalor.tweed5.patchwork.api; + +import de.siphalor.tweed5.patchwork.impl.PatchworkFactoryImpl; + +public interface PatchworkFactory { + static Builder builder() { + return new PatchworkFactoryImpl.Builder(); + } + + Patchwork create(); + + interface Builder { + PatchworkPartAccess registerPart(Class partClass); + PatchworkFactory build(); + } +} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkPartAccess.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkPartAccess.java new file mode 100644 index 0000000..043bbfd --- /dev/null +++ b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkPartAccess.java @@ -0,0 +1,6 @@ +package de.siphalor.tweed5.patchwork.api; + +import org.jspecify.annotations.Nullable; + +public interface PatchworkPartAccess { +} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkPartIsNullException.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkPartIsNullException.java deleted file mode 100644 index b463cff..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/api/PatchworkPartIsNullException.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.siphalor.tweed5.patchwork.api; - -public class PatchworkPartIsNullException extends RuntimeException { - public PatchworkPartIsNullException(String message) { - super(message); - } -} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/ByteArrayClassLoader.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/ByteArrayClassLoader.java deleted file mode 100644 index ae0579d..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/ByteArrayClassLoader.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl; - - -import org.jspecify.annotations.Nullable; - -public class ByteArrayClassLoader extends ClassLoader { - public static Class loadClass(@Nullable String binaryClassName, byte[] byteCode) { - return new ByteArrayClassLoader(ByteArrayClassLoader.class.getClassLoader()) - .createClass(binaryClassName, byteCode); - } - - private ByteArrayClassLoader(ClassLoader parent) { - super(parent); - } - - public Class createClass(@Nullable String binaryClassName, byte[] byteCode) { - Class clazz = defineClass(binaryClassName, byteCode, 0, byteCode.length); - resolveClass(clazz); - return clazz; - } -} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClass.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClass.java deleted file mode 100644 index 1dcc912..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClass.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl; - -import de.siphalor.tweed5.patchwork.api.Patchwork; -import lombok.Builder; -import lombok.Value; - -import java.lang.invoke.MethodHandle; -import java.util.Collection; - -@Value -@Builder -public class PatchworkClass

> { - String classPackage; - String className; - Class

theClass; - MethodHandle constructor; - Collection parts; -} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGenerator.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGenerator.java deleted file mode 100644 index 3e2011c..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGenerator.java +++ /dev/null @@ -1,653 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl; - -import de.siphalor.tweed5.patchwork.api.Patchwork; -import de.siphalor.tweed5.patchwork.api.PatchworkPartIsNullException; -import de.siphalor.tweed5.patchwork.impl.util.StreamUtils; -import lombok.*; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; -import org.objectweb.asm.*; -import org.objectweb.asm.commons.GeneratorAdapter; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@Getter -public class PatchworkClassGenerator { - /** - * Class version to use (Java 8) - */ - private static final int CLASS_VERSION = Opcodes.V1_8; - - private static final String INNER_EQUALS_METHOD_NAME = "patchwork$innerEquals"; - - private static String generateUniqueIdentifier() { - UUID uuid = UUID.randomUUID(); - return uuid.toString().replace("-", ""); - } - - private final Config config; - private final Collection parts; - private final String className; - @Getter(AccessLevel.NONE) - private final ClassWriter classWriter; - - public PatchworkClassGenerator(Config config, Collection parts) { - this.config = config; - this.parts = parts; - className = config.classPrefix() + generateUniqueIdentifier(); - classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - } - - public String internalClassName() { - return config.classPackage().replace('.', '/') + "/" + className(); - } - - public String binaryClassName() { - return config.classPackage() + "." + className(); - } - - public void verify() throws VerificationException { - for (PatchworkClassPart part : parts) { - verifyClass(part.partInterface()); - } - verifyPartMethods(); - } - - private void verifyClass(Class partClass) throws InvalidPatchworkPartClassException { - if (!partClass.isInterface()) { - throw new InvalidPatchworkPartClassException(partClass, "Must be an interface"); - } - if ((partClass.getModifiers() & Modifier.PUBLIC) == 0) { - throw new InvalidPatchworkPartClassException(partClass, "Interface must be public"); - } - } - - private void verifyPartMethods() throws DuplicateMethodsException { - Map> methodsBySignature = new HashMap<>(); - - for (PatchworkClassPart patchworkPart : parts) { - for (Method method : patchworkPart.partInterface().getMethods()) { - MethodDescriptor signature = new MethodDescriptor(method.getName(), method.getParameterTypes()); - - methodsBySignature - .computeIfAbsent(signature, s -> new ArrayList<>()) - .add(method); - } - } - - List duplicateMethods = methodsBySignature.entrySet().stream() - .filter(entry -> entry.getValue().size() > 1) - .flatMap(entry -> entry.getValue().stream()) - .collect(Collectors.toList()); - - if (!duplicateMethods.isEmpty()) { - throw new DuplicateMethodsException(duplicateMethods); - } - } - - public void generate() throws GenerationException { - beginClass(); - generateSimpleConstructor(); - for (PatchworkClassPart extensionClass : parts) { - addPart(extensionClass); - } - appendPojoMethods(); - appendDefaultPatchworkMethods(); - classWriter.visitEnd(); - } - - public byte[] emit() { - return classWriter.toByteArray(); - } - - private void beginClass() { - String[] interfaces = StreamUtils.concat( - Stream.of(Type.getInternalName(Patchwork.class)), - config.markerInterfaces().stream().map(Type::getInternalName), - parts.stream().map(ext -> Type.getInternalName(ext.partInterface())) - ).toArray(String[]::new); - - classWriter.visit( - CLASS_VERSION, - Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, - internalClassName(), - null, - Type.getInternalName(Object.class), - interfaces - ); - } - - private void generateSimpleConstructor() { - MethodVisitor methodWriter = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); - methodWriter.visitCode(); - methodWriter.visitVarInsn(Opcodes.ALOAD, 0); - methodWriter.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); - methodWriter.visitInsn(Opcodes.RETURN); - methodWriter.visitMaxs(1, 1); - methodWriter.visitEnd(); - } - - private void appendPojoMethods() { - appendEqualsMethod(); - appendHashCodeMethod(); - appendToStringMethod(); - } - - // - private void appendEqualsMethod() { - appendInnerEqualsMethod(); - - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PUBLIC, - "equals", - "(Ljava/lang/Object;)Z", - null, - null - ); - methodWriter.visitParameter("other", Opcodes.ACC_FINAL); - methodWriter.visitCode(); - - - Label falseLabel = methodWriter.newLabel(); - Label continueLabel = methodWriter.newLabel(); - - methodWriter.loadArg(0); - methodWriter.loadThis(); - methodWriter.visitJumpInsn(Opcodes.IF_ACMPNE, continueLabel); - methodWriter.push(true); - methodWriter.returnValue(); - - methodWriter.visitLabel(continueLabel); - methodWriter.loadArg(0); - methodWriter.visitTypeInsn(Opcodes.INSTANCEOF, internalClassName()); - methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel); - - methodWriter.loadArg(0); - methodWriter.visitTypeInsn(Opcodes.CHECKCAST, internalClassName()); - methodWriter.loadThis(); - methodWriter.visitMethodInsn( - Opcodes.INVOKESPECIAL, - internalClassName(), - INNER_EQUALS_METHOD_NAME, - "(L" + internalClassName() + ";)Z", - false - ); - methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel); - - methodWriter.push(true); - methodWriter.returnValue(); - - methodWriter.visitLabel(falseLabel); - methodWriter.push(false); - methodWriter.returnValue(); - - methodWriter.visitMaxs(0, 0); - methodWriter.visitEnd(); - } - - private void appendInnerEqualsMethod() { - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PRIVATE, - INNER_EQUALS_METHOD_NAME, - "(L" + internalClassName() + ";)Z", - null, - null - ); - - methodWriter.visitParameter("other", Opcodes.ACC_FINAL); - methodWriter.visitCode(); - - Label falseLabel = methodWriter.newLabel(); - for (PatchworkClassPart part : parts) { - methodWriter.loadArg(0); - visitFieldInsn(methodWriter, part, Opcodes.GETFIELD); - methodWriter.loadThis(); - visitFieldInsn(methodWriter, part, Opcodes.GETFIELD); - methodWriter.visitMethodInsn( - Opcodes.INVOKESTATIC, - Type.getInternalName(Objects.class), - "equals", - "(Ljava/lang/Object;Ljava/lang/Object;)Z", - false - ); - methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel); - } - - methodWriter.push(true); - methodWriter.returnValue(); - - methodWriter.visitLabel(falseLabel); - methodWriter.push(false); - methodWriter.returnValue(); - - methodWriter.visitMaxs(0, 0); - methodWriter.visitEnd(); - } - - private void appendHashCodeMethod() { - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PUBLIC, - "hashCode", - "()I", - null, - null - ); - - methodWriter.visitCode(); - - methodWriter.push(parts.size()); - methodWriter.newArray(Type.getType(Object.class)); - - int i = 0; - for (PatchworkClassPart part : parts) { - methodWriter.dup(); - methodWriter.push(i); - - methodWriter.loadThis(); - visitFieldInsn(methodWriter, part, Opcodes.GETFIELD); - - methodWriter.visitInsn(Opcodes.AASTORE); - - i++; - } - - methodWriter.visitMethodInsn( - Opcodes.INVOKESTATIC, - "java/util/Objects", - "hash", - "([Ljava/lang/Object;)I", - false - ); - - methodWriter.returnValue(); - - methodWriter.visitMaxs(0, 0); - methodWriter.visitEnd(); - } - - private void appendToStringMethod() { - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PUBLIC, - "toString", - "()Ljava/lang/String;", - null, - null - ); - - methodWriter.visitCode(); - - String stringBuilderType = Type.getInternalName(StringBuilder.class); - methodWriter.visitTypeInsn(Opcodes.NEW, stringBuilderType); - methodWriter.dup(); - methodWriter.push(className().length() + 10 + parts.size() * 64); - methodWriter.visitMethodInsn( - Opcodes.INVOKESPECIAL, - stringBuilderType, - "", - "(I)V", - false - ); - - StringBuilder constantConcat = new StringBuilder(); - constantConcat.append(className()).append("{\n"); - - for (PatchworkClassPart part : parts) { - constantConcat.append("\t").append(part.partInterface().getSimpleName()).append(": "); - methodWriter.push(constantConcat.toString()); - constantConcat.setLength(0); - visitStringBuilderAppendString(methodWriter); - - Label nullLabel = methodWriter.newLabel(); - Label continueLabel = methodWriter.newLabel(); - methodWriter.loadThis(); - visitFieldInsn(methodWriter, part, Opcodes.GETFIELD); - methodWriter.dup(); - methodWriter.visitJumpInsn(Opcodes.IFNULL, nullLabel); - - visitToString(methodWriter); - methodWriter.visitJumpInsn(Opcodes.GOTO, continueLabel); - - methodWriter.visitLabel(nullLabel); - methodWriter.pop(); - methodWriter.push(""); - - methodWriter.visitLabel(continueLabel); - visitStringBuilderAppendString(methodWriter); - - constantConcat.append(",\n"); - } - constantConcat.append("}"); - methodWriter.push(constantConcat.toString()); - visitStringBuilderAppendString(methodWriter); - visitToString(methodWriter); - methodWriter.returnValue(); - - methodWriter.visitMaxs(0, 0); - methodWriter.visitEnd(); - } - // - - private void appendDefaultPatchworkMethods() { - appendCopyMethod(); - appendIsPatchworkPartDefinedMethod(); - appendIsPatchworkPartSetMethod(); - } - - // - private void appendCopyMethod() { - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PUBLIC, - "copy", - "()L" + Type.getInternalName(Patchwork.class) + ";", - null, - null - ); - methodWriter.visitCode(); - methodWriter.visitTypeInsn(Opcodes.NEW, internalClassName()); - methodWriter.dup(); - methodWriter.visitMethodInsn( - Opcodes.INVOKESPECIAL, - internalClassName(), - "", - "()V", - false - ); - for (PatchworkClassPart part : parts) { - methodWriter.dup(); - methodWriter.loadThis(); - visitFieldInsn(methodWriter, part, Opcodes.GETFIELD); - visitFieldInsn(methodWriter, part, Opcodes.PUTFIELD); - } - methodWriter.returnValue(); - methodWriter.visitMaxs(0, 0); - methodWriter.visitEnd(); - } - - private void appendIsPatchworkPartDefinedMethod() { - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PUBLIC, - "isPatchworkPartDefined", - "(Ljava/lang/Class;)Z", - null, - null - ); - methodWriter.visitParameter(null, Opcodes.ACC_FINAL); - methodWriter.visitCode(); - Label trueLabel = methodWriter.newLabel(); - for (PatchworkClassPart part : parts) { - methodWriter.loadArg(0); - methodWriter.push(Type.getType(part.partInterface())); - methodWriter.ifCmp(Type.getType(Object.class), GeneratorAdapter.EQ, trueLabel); - } - methodWriter.push(false); - methodWriter.returnValue(); - methodWriter.visitLabel(trueLabel); - methodWriter.push(true); - methodWriter.returnValue(); - methodWriter.visitMaxs(0, 0); - methodWriter.visitEnd(); - } - - private void appendIsPatchworkPartSetMethod() { - GeneratorAdapter methodWriter = createMethod( - Opcodes.ACC_PUBLIC, - "isPatchworkPartSet", - "(Ljava/lang/Class;)Z", - null, - null - ); - methodWriter.visitParameter(null, Opcodes.ACC_FINAL); - methodWriter.visitCode(); - Label[] labels = new Label[parts.size()]; - int i = 0; - for (PatchworkClassPart part : parts) { - labels[i] = methodWriter.newLabel(); - methodWriter.loadArg(0); - methodWriter.push(Type.getType(part.partInterface())); - methodWriter.ifCmp(Type.getType(Object.class), GeneratorAdapter.EQ, labels[i]); - i++; - } - methodWriter.push(false); - methodWriter.returnValue(); - - Label falseLabel = methodWriter.newLabel(); - i = 0; - for (PatchworkClassPart part : parts) { - methodWriter.visitLabel(labels[i]); - methodWriter.loadThis(); - visitFieldInsn(methodWriter, part, Opcodes.GETFIELD); - methodWriter.push((String) null); - methodWriter.ifCmp(Type.getType(part.partInterface()), GeneratorAdapter.EQ, falseLabel); - methodWriter.push(true); - methodWriter.returnValue(); - i++; - } - methodWriter.visitLabel(falseLabel); - methodWriter.push(false); - methodWriter.returnValue(); - methodWriter.visitMaxs(1, 1); - methodWriter.visitEnd(); - } - - public void addPart(PatchworkClassPart patchworkPart) throws GenerationException { - patchworkPart.fieldName("f_" + generateUniqueIdentifier()); - - classWriter.visitField( - Opcodes.ACC_PUBLIC, - patchworkPart.fieldName(), - patchworkPart.partInterface().descriptorString(), - null, - null - ); - - appendPartMethods(patchworkPart); - } - - private void appendPartMethods(PatchworkClassPart patchworkPart) throws GenerationException { - try { - ClassReader classReader = new ClassReader(patchworkPart.partInterface().getName()); - classReader.accept(new PartClassVisitor(patchworkPart), ClassReader.SKIP_FRAMES); - } catch (IOException e) { - throw new GenerationException("Failed to read interface class file", e); - } - } - // - - private GeneratorAdapter createMethod( - int access, - String name, - String desc, - @Nullable String signature, - String @Nullable [] exceptions - ) { - MethodVisitor methodVisitor = classWriter.visitMethod(access, name, desc, signature, exceptions); - return new GeneratorAdapter(methodVisitor, access, name, desc); - } - - private void visitFieldInsn(MethodVisitor methodWriter, PatchworkClassPart part, int opcode) { - methodWriter.visitFieldInsn( - opcode, - internalClassName(), - part.fieldName(), - Type.getDescriptor(part.partInterface()) - ); - } - - private static void visitToString(MethodVisitor methodWriter) { - methodWriter.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - Type.getInternalName(Object.class), - "toString", - "()Ljava/lang/String;", - false - ); - } - - private static void visitStringBuilderAppendString(MethodVisitor methodWriter) { - String stringBuilderType = Type.getInternalName(StringBuilder.class); - methodWriter.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - stringBuilderType, - "append", - "(Ljava/lang/String;)L" + stringBuilderType + ";", - false - ); - } - - private class PartClassVisitor extends ClassVisitor { - private final PatchworkClassPart extensionClass; - - protected PartClassVisitor(PatchworkClassPart extensionClass) { - super(Opcodes.ASM9); - this.extensionClass = extensionClass; - } - - @Override - public MethodVisitor visitMethod( - int access, - String name, - String descriptor, - String signature, - String[] exceptions - ) { - GeneratorAdapter methodWriter = createMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions); - return new PartMethodVisitor(api, methodWriter, descriptor, extensionClass); - } - } - - private class PartMethodVisitor extends MethodVisitor { - private final GeneratorAdapter methodWriter; - private final String methodDescriptor; - private final PatchworkClassPart patchworkPart; - - protected PartMethodVisitor( - int api, - GeneratorAdapter methodWriter, - String methodDescriptor, - PatchworkClassPart patchworkPart - ) { - super(api); - this.methodWriter = methodWriter; - this.patchworkPart = patchworkPart; - this.methodDescriptor = methodDescriptor; - } - - @Override - public void visitParameter(String name, int access) { - methodWriter.visitParameter(name, access); - } - - @Override - public void visitEnd() { - Label nullLabel = methodWriter.newLabel(); - - methodWriter.visitCode(); - methodWriter.visitVarInsn(Opcodes.ALOAD, 0); - methodWriter.visitFieldInsn( - Opcodes.GETFIELD, - internalClassName(), - patchworkPart.fieldName(), - Type.getDescriptor(patchworkPart.partInterface()) - ); - methodWriter.dup(); - methodWriter.ifNull(nullLabel); - methodWriter.loadArgs(); - methodWriter.visitMethodInsn( - Opcodes.INVOKEINTERFACE, - Type.getInternalName(patchworkPart.partInterface()), - methodWriter.getName(), - methodDescriptor, - true - ); - methodWriter.returnValue(); - methodWriter.visitLabel(nullLabel); - methodWriter.pop(); - methodWriter.throwException( - Type.getType(PatchworkPartIsNullException.class), - "The patchwork part " + patchworkPart.partInterface().getSimpleName() + " has not been set" - ); - - methodWriter.visitMaxs(-1, -1); - methodWriter.visitEnd(); - } - } - - @Value - private static class MethodDescriptor { - String name; - Class[] parameterTypes; - } - - @Data - public static class Config { - @lombok.NonNull - private @NonNull String classPackage; - private String classPrefix = ""; - private Collection> markerInterfaces = Collections.emptyList(); - } - - public static class VerificationException extends Exception { - private VerificationException(String message) { - super(message); - } - } - - @Value - @EqualsAndHashCode(callSuper = true) - public static class InvalidPatchworkPartClassException extends VerificationException { - Class partClass; - - public InvalidPatchworkPartClassException(Class partClass, String message) { - super("Invalid patchwork part class " + partClass.getName() + ": " + message); - this.partClass = partClass; - } - - @Override - public String toString() { - return super.toString(); - } - } - - @Value - @EqualsAndHashCode(callSuper = true) - public static class DuplicateMethodsException extends VerificationException { - transient Collection signatures; - - private DuplicateMethodsException(Collection methods) { - super("Duplicate method signatures:\n" + methods.stream() - .map(DuplicateMethodsException::getMethodMessage) - .collect(Collectors.joining("\n"))); - this.signatures = methods; - } - - private static String getMethodMessage(Method method) { - StringBuilder stringBuilder = new StringBuilder("\t- " - + method.getDeclaringClass().getCanonicalName() - + "#("); - for (Class parameterType : method.getParameterTypes()) { - stringBuilder.append(parameterType.getCanonicalName()); - stringBuilder.append(", "); - } - stringBuilder.append(")"); - stringBuilder.append(method.getReturnType().getCanonicalName()); - stringBuilder.append("\n"); - return stringBuilder.toString(); - } - - @Override - public String toString() { - return super.toString(); - } - } - - public static class GenerationException extends Exception { - public GenerationException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassPart.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassPart.java deleted file mode 100644 index 93262d8..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassPart.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl; - -import lombok.Data; - -import java.lang.invoke.MethodHandle; - -@Data -public class PatchworkClassPart { - private final Class partInterface; - private String fieldName; - private MethodHandle fieldSetter; -} \ No newline at end of file diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkFactoryImpl.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkFactoryImpl.java new file mode 100644 index 0000000..12361b9 --- /dev/null +++ b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/PatchworkFactoryImpl.java @@ -0,0 +1,100 @@ +package de.siphalor.tweed5.patchwork.impl; + +import de.siphalor.tweed5.patchwork.api.InvalidPatchworkAccessException; +import de.siphalor.tweed5.patchwork.api.Patchwork; +import de.siphalor.tweed5.patchwork.api.PatchworkFactory; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class PatchworkFactoryImpl implements PatchworkFactory { + private final UUID factoryUuid; + private final Class[] partClasses; + + @Override + public Patchwork create() { + return new PatchworkImpl(new Object[partClasses.length]); + } + + public static class Builder implements PatchworkFactory.Builder { + private final UUID factoryUuid = UUID.randomUUID(); + private final List> partClasses = new ArrayList<>(); + private boolean built; + + @Override + public PatchworkPartAccess registerPart(Class partClass) { + requireFresh(); + partClasses.add(partClass); + return new PartAccessImpl<>(factoryUuid, partClasses.size() - 1); + } + + @Override + public PatchworkFactory build() { + requireFresh(); + built = true; + return new PatchworkFactoryImpl(factoryUuid, partClasses.toArray(new Class[0])); + } + + private void requireFresh() { + if (built) { + throw new IllegalStateException("Builder has already been used."); + } + } + } + + @RequiredArgsConstructor + private static class PartAccessImpl implements PatchworkPartAccess { + private final UUID factoryUuid; + private final int partIndex; + } + + @RequiredArgsConstructor + @EqualsAndHashCode + private class PatchworkImpl implements Patchwork { + private final @Nullable Object[] partValues; + + @Override + public T get(PatchworkPartAccess access) throws InvalidPatchworkAccessException { + PartAccessImpl castedAccess = validatePartAccess(access); + //noinspection unchecked + return (T) partValues[castedAccess.partIndex]; + } + + @Override + public void set(PatchworkPartAccess access, T value) throws InvalidPatchworkAccessException { + PartAccessImpl castedAccess = validatePartAccess(access); + + if (value != null && !partClasses[castedAccess.partIndex].isInstance(value)) { + throw new IllegalArgumentException( + "value " + value + " of type " + value.getClass().getName() + + " doesn't match registered value class " + partClasses[castedAccess.partIndex].getName() + ); + } + + partValues[castedAccess.partIndex] = value; + } + + private PartAccessImpl validatePartAccess(PatchworkPartAccess access) + throws InvalidPatchworkAccessException { + if (!(access instanceof PartAccessImpl)) { + throw new InvalidPatchworkAccessException("Part access is of incorrect class."); + } else if (((PartAccessImpl) access).factoryUuid != factoryUuid) { + throw new InvalidPatchworkAccessException("Part access does not belong to the same patchwork factory."); + } + return (PartAccessImpl) access; + } + + @Override + public Patchwork copy() { + return new PatchworkImpl(Arrays.copyOf(partValues, partValues.length)); + } + } +} diff --git a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/util/StreamUtils.java b/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/util/StreamUtils.java deleted file mode 100644 index 564ed90..0000000 --- a/tweed5-patchwork/src/main/java/de/siphalor/tweed5/patchwork/impl/util/StreamUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl.util; - -import java.util.stream.Stream; - -public class StreamUtils { - @SafeVarargs - public static Stream concat(Stream... streams) { - Stream result = Stream.empty(); - for (Stream stream : streams) { - result = Stream.concat(result, stream); - } - return result; - } -} diff --git a/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGeneratorGeneratedClassTest.java b/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGeneratorGeneratedClassTest.java deleted file mode 100644 index 0385e95..0000000 --- a/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGeneratorGeneratedClassTest.java +++ /dev/null @@ -1,269 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl; - -import de.siphalor.tweed5.patchwork.api.Patchwork; -import de.siphalor.tweed5.patchwork.api.PatchworkPartIsNullException; -import lombok.Data; -import lombok.experimental.Accessors; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; - -class PatchworkClassGeneratorGeneratedClassTest { - - PatchworkClassPart partA; - PatchworkClassPart partB; - - byte[] bytes; - Class> patchworkClass; - Patchwork patchwork; - - @BeforeEach - void setUp() { - partA = new PatchworkClassPart(ExtensionA.class); - partB = new PatchworkClassPart(ExtensionB.class); - PatchworkClassGenerator generator = new PatchworkClassGenerator( - new PatchworkClassGenerator.Config("de.siphalor.tweed5.core.test") - .classPrefix("FullTest$") - .markerInterfaces(Collections.singletonList(MarkerInterface.class)), - Arrays.asList(partA, partB) - ); - - assertDoesNotThrow(generator::verify); - assertDoesNotThrow(generator::generate); - - bytes = generator.emit(); - //noinspection unchecked - patchworkClass = (Class>) assertDoesNotThrow(() -> ByteArrayClassLoader.loadClass(null, bytes)); - - patchwork = createPatchworkInstance(); - } - - @Test - @Disabled("Dumping the class is only for testing purposes") - void dumpClass() { - try { - Path target = File.createTempFile("tweed5-patchwork", ".class").toPath(); - try (OutputStream os = Files.newOutputStream(target)) { - os.write(bytes); - } - System.out.println("Dumped generated class to " + target); - - } catch (IOException e) { - assertNull(e, "Must not throw exception"); - } - } - - @Test - void packageName() { - assertEquals("de.siphalor.tweed5.core.test", patchworkClass.getPackage().getName()); - } - - @Test - void className() { - assertTrue(patchworkClass.getSimpleName().startsWith("FullTest$"), "Generated class name must start with prefix FullTest$, got " + patchworkClass.getSimpleName()); - } - - @Test - void implementsInterfaces() { - assertImplements(MarkerInterface.class, patchworkClass); - assertImplements(Patchwork.class, patchworkClass); - assertImplements(ExtensionA.class, patchworkClass); - assertImplements(ExtensionB.class, patchworkClass); - } - - @Test - void toStringMethod() { - int defaultHashCode = System.identityHashCode(patchwork); - String stringResult = patchwork.toString(); - assertFalse(stringResult.contains(Integer.toHexString(defaultHashCode)), "Expected toString not to be the default toString, got: " + stringResult); - } - - @Test - void toStringContent() { - setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl()); - ((ExtensionA) patchwork).setText("Hello World!"); - String stringResult = patchwork.toString(); - assertTrue(stringResult.contains("Hello World!"), "Expected toString to contain the toString of its extensions, got: " + stringResult); - } - - @Test - void hashCodeMethod() { - int emptyHashCode = patchwork.hashCode(); - - setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl()); - int hashCodeWithA = patchwork.hashCode(); - - ((ExtensionA) patchwork).setText("Hello World!"); - int hashCodeWithAContent = patchwork.hashCode(); - - assertNotEquals(emptyHashCode, hashCodeWithA, "Expected hashCode to be different, got: " + emptyHashCode + " and " + hashCodeWithA); - assertNotEquals(emptyHashCode, hashCodeWithAContent, "Expected hashCode to be different, got: " + emptyHashCode + " and " + hashCodeWithAContent); - assertNotEquals(hashCodeWithA, hashCodeWithAContent, "Expected hashCode to be different, got: " + hashCodeWithA + " and " + hashCodeWithAContent); - } - - @SuppressWarnings("SimplifiableAssertion") - @Test - void equalsMethod() { - //noinspection ConstantValue,SimplifiableAssertion - assertFalse(patchwork.equals(null)); - - //noinspection EqualsWithItself - assertTrue(patchwork.equals(patchwork)); - - Patchwork other = createPatchworkInstance(); - assertTrue(patchwork.equals(other)); - assertTrue(other.equals(patchwork)); - - setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl()); - assertFalse(patchwork.equals(other)); - - setFieldValue(other, partA.fieldName(), new ExtensionAImpl()); - assertTrue(patchwork.equals(other)); - assertTrue(other.equals(patchwork)); - - ((ExtensionA) patchwork).setText("Hello 1!"); - ((ExtensionA) other).setText("Hello 2!"); - assertFalse(patchwork.equals(other)); - } - - @Test - void copy() { - ExtensionAImpl aImpl = new ExtensionAImpl(); - setFieldValue(patchwork, partA.fieldName(), aImpl); - - Patchwork copy = patchwork.copy(); - Object aValue = getFieldValue(copy, partA.fieldName()); - Object bValue = getFieldValue(copy, partB.fieldName()); - - assertSame(aImpl, aValue); - assertNull(bValue); - } - - @Test - void isPartDefined() { - assertPartDefined(ExtensionA.class, patchwork); - assertPartDefined(ExtensionB.class, patchwork); - assertPartUndefined(ExtensionC.class, patchwork); - } - - static void assertPartDefined(Class anInterface, Patchwork patchwork) { - assertTrue(patchwork.isPatchworkPartDefined(anInterface), "Patchwork part " + anInterface.getName() + " must be defined"); - } - - static void assertPartUndefined(Class anInterface, Patchwork patchwork) { - assertFalse(patchwork.isPatchworkPartDefined(anInterface), "Patchwork part " + anInterface.getName() + " must not be defined"); - } - - @Test - void checkFieldsExist() { - assertFieldExists(partA.fieldName(), ExtensionA.class); - assertFieldExists(partB.fieldName(), ExtensionB.class); - } - - void assertFieldExists(String fieldName, Class fieldType) { - Field field = getField(fieldName); - assertEquals(fieldType, field.getType()); - } - - @Test - void isPartSet() { - assertPartUnset(ExtensionA.class, patchwork); - assertPartUnset(ExtensionB.class, patchwork); - assertPartUnset(ExtensionC.class, patchwork); - - setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl()); - - assertPartSet(ExtensionA.class, patchwork); - assertPartUnset(ExtensionB.class, patchwork); - assertPartUnset(ExtensionC.class, patchwork); - - setFieldValue(patchwork, partB.fieldName(), new ExtensionBImpl()); - - assertPartSet(ExtensionA.class, patchwork); - assertPartSet(ExtensionB.class, patchwork); - assertPartUnset(ExtensionC.class, patchwork); - } - - @Test - void inheritedMethodCalls() { - assertThrows(PatchworkPartIsNullException.class, () -> ((ExtensionA) patchwork).getText()); - assertThrows(PatchworkPartIsNullException.class, () -> ((ExtensionB) patchwork).multiply(1, 2)); - - setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl()); - setFieldValue(patchwork, partB.fieldName(), new ExtensionBImpl()); - - assertEquals(6, assertDoesNotThrow(() -> ((ExtensionB) patchwork).multiply(2, 3)), "Method of extension b must be working"); - - assertDoesNotThrow(() -> ((ExtensionA) patchwork).setText("something")); - assertEquals("something", ((ExtensionA) patchwork).getText()); - } - - Patchwork createPatchworkInstance() { - Constructor constructor = assertDoesNotThrow(() -> patchworkClass.getConstructor(), "The generated class' constructor must be accessible"); - return assertDoesNotThrow(() -> (Patchwork) constructor.newInstance()); - } - - Object getFieldValue(Patchwork patchwork, String fieldName) { - return assertDoesNotThrow(() -> getField(fieldName).get(patchwork)); - } - - void setFieldValue(Patchwork patchwork, String fieldName, Object value) { - assertDoesNotThrow(() -> getField(fieldName).set(patchwork, value), "Field " + fieldName + " must be accessible"); - } - - Field getField(String fieldName) { - return assertDoesNotThrow(() -> patchworkClass.getField(fieldName), "Field " + fieldName + " must exist and be public"); - } - - static void assertPartSet(Class anInterface, Patchwork patchwork) { - assertTrue(patchwork.isPatchworkPartSet(anInterface), "Patchwork part " + anInterface.getName() + " must be set"); - } - - static void assertPartUnset(Class anInterface, Patchwork patchwork) { - assertFalse(patchwork.isPatchworkPartSet(anInterface), "Patchwork part " + anInterface.getName() + " must not be set"); - } - - static void assertImplements(Class anInterface, Class theClass) { - assertTrue(anInterface.isAssignableFrom(theClass), "Class " + theClass.getName() + " must implement " + anInterface.getName()); - } - - public interface MarkerInterface {} - - public interface ExtensionA { - String getText(); - - void setText(String text); - } - - @Data - @Accessors(fluent = false) - static class ExtensionAImpl implements ExtensionA { - private String text; - } - - public interface ExtensionB { - int multiply(int a, int b); - } - - static class ExtensionBImpl implements ExtensionB { - public int multiply(int a, int b) { - return a * b; - } - } - - public interface ExtensionC { - void test(); - } -} \ No newline at end of file diff --git a/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGeneratorTest.java b/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGeneratorTest.java deleted file mode 100644 index a29170d..0000000 --- a/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkClassGeneratorTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.siphalor.tweed5.patchwork.impl; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -class PatchworkClassGeneratorTest { - - @Test - void notAnInterface() { - PatchworkClassGenerator generator = createGenerator(Collections.singletonList(NotAnInterface.class)); - assertThrows(PatchworkClassGenerator.VerificationException.class, generator::verify); - } - - @Test - void nonPublicInterface() { - PatchworkClassGenerator generator = createGenerator(Collections.singletonList(NonPublicInterface.class)); - assertThrows(PatchworkClassGenerator.VerificationException.class, generator::verify); - } - - @Test - void duplicateFields() { - PatchworkClassGenerator generator = createGenerator(Arrays.asList(DuplicateA.class, DuplicateB.class)); - assertThrows(PatchworkClassGenerator.VerificationException.class, generator::verify); - } - - PatchworkClassGenerator createGenerator(Collection> partClasses) { - return new PatchworkClassGenerator( - new PatchworkClassGenerator.Config("de.siphalor.tweed5.patchwork.test.generated"), - partClasses.stream().map(PatchworkClassPart::new).collect(Collectors.toList()) - ); - } - - public static class NotAnInterface { - } - - interface NonPublicInterface { - } - - public interface DuplicateA { - void test(); - } - - public interface DuplicateB { - void test(); - } -} \ No newline at end of file diff --git a/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkFactoryImplTest.java b/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkFactoryImplTest.java new file mode 100644 index 0000000..0aa69c7 --- /dev/null +++ b/tweed5-patchwork/src/test/java/de/siphalor/tweed5/patchwork/impl/PatchworkFactoryImplTest.java @@ -0,0 +1,94 @@ +package de.siphalor.tweed5.patchwork.impl; + +import de.siphalor.tweed5.patchwork.api.InvalidPatchworkAccessException; +import de.siphalor.tweed5.patchwork.api.Patchwork; +import de.siphalor.tweed5.patchwork.api.PatchworkFactory; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PatchworkFactoryImplTest { + @Test + void test() { + PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder(); + PatchworkPartAccess stringAccess1 = builder.registerPart(String.class); + PatchworkPartAccess<@Nullable Number> integerAccess1 = builder.registerPart(Number.class); + PatchworkPartAccess stringAccess2 = builder.registerPart(String.class); + PatchworkFactory factory = builder.build(); + + Patchwork patchwork = factory.create(); + + assertThat(patchwork.get(stringAccess1)).isNull(); + assertThat(patchwork.get(integerAccess1)).isNull(); + assertThat(patchwork.get(stringAccess2)).isNull(); + + patchwork.set(stringAccess1, "Hello"); + patchwork.set(stringAccess2, "World"); + patchwork.set(integerAccess1, 123); + + assertThat(patchwork.get(stringAccess1)).isEqualTo("Hello"); + assertThat(patchwork.get(integerAccess1)).isEqualTo(123); + assertThat(patchwork.get(stringAccess2)).isEqualTo("World"); + + patchwork.set(integerAccess1, null); + assertThat(patchwork.get(integerAccess1)).isNull(); + } + + @Test + void copy() { + PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder(); + PatchworkPartAccess stringAccess = builder.registerPart(String.class); + PatchworkFactory factory = builder.build(); + + Patchwork patchwork = factory.create(); + patchwork.set(stringAccess, "Hello"); + + Patchwork copy = patchwork.copy(); + assertThat(copy.get(stringAccess)).isEqualTo("Hello"); + assertThat(copy).isEqualTo(patchwork); + } + + @Test + void lateRegister() { + PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder(); + builder.build(); + assertThatThrownBy(() -> builder.registerPart(String.class)).isInstanceOf(IllegalStateException.class); + } + + @Test + void doubleBuild() { + PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder(); + builder.build(); + assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class); + } + + @Test + void invalidAccess() { + PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder(); + builder.registerPart(String.class); + PatchworkFactory factory = builder.build(); + + Patchwork patchwork = factory.create(); + + PatchworkFactoryImpl.Builder otherBuilder = new PatchworkFactoryImpl.Builder(); + PatchworkPartAccess otherAccess = otherBuilder.registerPart(String.class); + otherBuilder.build(); + + assertThatThrownBy(() -> patchwork.get(otherAccess)).isInstanceOf(InvalidPatchworkAccessException.class); + assertThatThrownBy(() -> patchwork.set(otherAccess, "Hello")).isInstanceOf(InvalidPatchworkAccessException.class); + } + + @Test + void setWrongType() { + PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder(); + //noinspection unchecked + PatchworkPartAccess access = ((PatchworkPartAccess)(Object) builder.registerPart(String.class)); + PatchworkFactory factory = builder.build(); + + Patchwork patchwork = factory.create(); + assertThatThrownBy(() -> patchwork.set(access, 123)).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/EntryReaderWriterDefinition.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/EntryReaderWriterDefinition.java deleted file mode 100644 index dc63487..0000000 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/EntryReaderWriterDefinition.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.siphalor.tweed5.data.extension.api; - -public interface EntryReaderWriterDefinition { - TweedEntryReader reader(); - TweedEntryWriter writer(); -} diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteEntryDataExtension.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteEntryDataExtension.java deleted file mode 100644 index 1d9280d..0000000 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteEntryDataExtension.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.siphalor.tweed5.data.extension.api; - -public interface ReadWriteEntryDataExtension { - TweedEntryReader entryReaderChain(); - TweedEntryWriter entryWriterChain(); -} diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java index 25ff1e0..e9f4a0e 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java @@ -2,29 +2,120 @@ package de.siphalor.tweed5.data.extension.api; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.extension.TweedExtension; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; +import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter; import de.siphalor.tweed5.data.extension.impl.ReadWriteExtensionImpl; import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; +import de.siphalor.tweed5.patchwork.api.Patchwork; import org.jspecify.annotations.Nullable; +import java.util.function.Consumer; +import java.util.function.Function; + public interface ReadWriteExtension extends TweedExtension { Class DEFAULT = ReadWriteExtensionImpl.class; - void setEntryReaderWriterDefinition(ConfigEntry entry, EntryReaderWriterDefinition readerWriterDefinition); + static Consumer> entryReaderWriter( + TweedEntryReaderWriter> entryReaderWriter + ) { + return entryReaderWriter(entryReaderWriter, entryReaderWriter); + } - ReadWriteContextExtensionsData createReadWriteContextExtensionsData(); + static Consumer> entryReaderWriter( + TweedEntryReader> entryReader, + TweedEntryWriter> entryWriter + ) { + return entry -> { + ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class) + .orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present")); + extension.setEntryReader(entry, entryReader); + extension.setEntryWriter(entry, entryWriter); + }; + } - T read( + static Consumer> entryReader(TweedEntryReader> entryReader) { + return entry -> { + ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class) + .orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present")); + extension.setEntryReader(entry, entryReader); + }; + } + + static Consumer> entryWriter(TweedEntryWriter> entryWriter) { + return entry -> { + ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class) + .orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present")); + extension.setEntryWriter(entry, entryWriter); + }; + } + + static Function, T> read(TweedDataReader reader) { + return read(reader, null); + } + + static Function, T> read( TweedDataReader reader, + @Nullable Consumer contextExtensionsDataCustomizer + ) { + return entry -> { + try { + ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class) + .orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present")); + Patchwork contextExtensionsData = extension.createReadWriteContextExtensionsData(); + if (contextExtensionsDataCustomizer != null) { + contextExtensionsDataCustomizer.accept(contextExtensionsData); + } + return extension.read(reader, entry, contextExtensionsData); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + + static Consumer> write(TweedDataVisitor writer, T value) { + return write(writer, value, null); + } + + static Consumer> write( + TweedDataVisitor writer, + T value, + @Nullable Consumer contextExtensionsDataCustomizer + ) { + return entry -> { + try { + ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class) + .orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present")); + Patchwork contextExtensionsData = extension.createReadWriteContextExtensionsData(); + if (contextExtensionsDataCustomizer != null) { + contextExtensionsDataCustomizer.accept(contextExtensionsData); + } + extension.write(writer, value, entry, contextExtensionsData); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + + > void setEntryReaderWriter( ConfigEntry entry, - ReadWriteContextExtensionsData contextExtensionsData - ) throws TweedEntryReadException; + TweedEntryReader entryReader, + TweedEntryWriter entryWriter + ); + > void setEntryReader(ConfigEntry entry, TweedEntryReader entryReader); + > void setEntryWriter(ConfigEntry entry, TweedEntryWriter entryWriter); + + Patchwork createReadWriteContextExtensionsData(); + + T read(TweedDataReader reader, ConfigEntry entry, Patchwork contextExtensionsData) + throws TweedEntryReadException; void write( TweedDataVisitor writer, T value, ConfigEntry entry, - ReadWriteContextExtensionsData contextExtensionsData + Patchwork contextExtensionsData ) throws TweedEntryWriteException; + + > TweedEntryReader getReaderChain(C entry); + > TweedEntryWriter getWriterChain(C entry); } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedReadContext.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedReadContext.java index 4e704c3..8badb75 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedReadContext.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedReadContext.java @@ -1,7 +1,8 @@ package de.siphalor.tweed5.data.extension.api; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; +import de.siphalor.tweed5.patchwork.api.Patchwork; public interface TweedReadContext { - ReadWriteContextExtensionsData extensionsData(); + ReadWriteExtension readWriteExtension(); + Patchwork extensionsData(); } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedWriteContext.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedWriteContext.java index fdd33f8..5c955de 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedWriteContext.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedWriteContext.java @@ -1,7 +1,8 @@ package de.siphalor.tweed5.data.extension.api; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; +import de.siphalor.tweed5.patchwork.api.Patchwork; public interface TweedWriteContext { - ReadWriteContextExtensionsData extensionsData(); + ReadWriteExtension readWriteExtension(); + Patchwork extensionsData(); } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteContextExtensionsData.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteContextExtensionsData.java deleted file mode 100644 index bad271e..0000000 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteContextExtensionsData.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.siphalor.tweed5.data.extension.api.extension; - -import de.siphalor.tweed5.patchwork.api.Patchwork; - -public interface ReadWriteContextExtensionsData extends Patchwork { -} diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteExtensionSetupContext.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteExtensionSetupContext.java index 8fa5b58..8eac58a 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteExtensionSetupContext.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/extension/ReadWriteExtensionSetupContext.java @@ -1,7 +1,7 @@ package de.siphalor.tweed5.data.extension.api.extension; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; public interface ReadWriteExtensionSetupContext { - RegisteredExtensionData registerReadWriteContextExtensionData(Class extensionDataClass); + PatchworkPartAccess registerReadWriteContextExtensionData(Class extensionDataClass); } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/readwrite/TweedEntryReaderWriter.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/readwrite/TweedEntryReaderWriter.java index b208b03..fbf4c3d 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/readwrite/TweedEntryReaderWriter.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/readwrite/TweedEntryReaderWriter.java @@ -3,5 +3,6 @@ package de.siphalor.tweed5.data.extension.api.readwrite; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.data.extension.api.TweedEntryReader; import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; +import org.jspecify.annotations.Nullable; -public interface TweedEntryReaderWriter> extends TweedEntryReader, TweedEntryWriter {} +public interface TweedEntryReaderWriter> extends TweedEntryReader, TweedEntryWriter {} diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java index 3379e91..2298022 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java @@ -3,56 +3,41 @@ package de.siphalor.tweed5.data.extension.impl; import com.google.auto.service.AutoService; import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer; import de.siphalor.tweed5.data.extension.api.*; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.dataapi.api.TweedDataReadException; import de.siphalor.tweed5.dataapi.api.TweedDataReader; -import de.siphalor.tweed5.dataapi.api.TweedDataWriteException; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; +import de.siphalor.tweed5.dataapi.api.TweedDataWriteException; import de.siphalor.tweed5.patchwork.api.Patchwork; -import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClass; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart; -import lombok.Setter; -import lombok.Value; +import de.siphalor.tweed5.patchwork.api.PatchworkFactory; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import lombok.Data; import lombok.val; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import java.lang.invoke.MethodHandle; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; @AutoService(ReadWriteExtension.class) public class ReadWriteExtensionImpl implements ReadWriteExtension { private final ConfigContainer configContainer; - private final RegisteredExtensionData - readerWriterDefinitionExtension; - private final RegisteredExtensionData readWriteEntryDataExtension; + private final PatchworkPartAccess customEntryDataAccess; private DefaultMiddlewareContainer> entryReaderMiddlewareContainer = new DefaultMiddlewareContainer<>(); private DefaultMiddlewareContainer> entryWriterMiddlewareContainer = new DefaultMiddlewareContainer<>(); - private final Map, RegisteredExtensionDataImpl> - readWriteContextExtensionsDataClasses - = new HashMap<>(); - private @Nullable PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork; + private @Nullable PatchworkFactory readWriteContextPatchworkFactory; public ReadWriteExtensionImpl(ConfigContainer configContainer, TweedExtensionSetupContext context) { this.configContainer = configContainer; - this.readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class); - this.readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class); + this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class); } @Override @@ -64,23 +49,8 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { public void extensionsFinalized() { Collection extensions = configContainer.extensions(); - ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() { - @Override - public RegisteredExtensionData registerReadWriteContextExtensionData( - Class extensionDataClass - ) { - if (readWriteContextExtensionsDataClasses.containsKey(extensionDataClass)) { - throw new IllegalArgumentException("Context extension " - + extensionDataClass.getName() - + " is already registered"); - } - RegisteredExtensionDataImpl - registeredExtensionData - = new RegisteredExtensionDataImpl<>(); - readWriteContextExtensionsDataClasses.put(extensionDataClass, registeredExtensionData); - return registeredExtensionData; - } - }; + PatchworkFactory.Builder readWriteContextPatchworkFactorBuilder = PatchworkFactory.builder(); + ReadWriteExtensionSetupContext setupContext = readWriteContextPatchworkFactorBuilder::registerPart; entryReaderMiddlewareContainer = new DefaultMiddlewareContainer<>(); entryWriterMiddlewareContainer = new DefaultMiddlewareContainer<>(); @@ -102,79 +72,62 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { } } + readWriteContextPatchworkFactory = readWriteContextPatchworkFactorBuilder.build(); + entryReaderMiddlewareContainer.seal(); entryWriterMiddlewareContainer.seal(); + } - val patchworkClassCreator = PatchworkClassCreator.builder() - .patchworkInterface(ReadWriteContextExtensionsData.class) - .classPackage("de.siphalor.tweed5.data.extension.generated") - .classPrefix("ReadWriteContextExtensionsData$") - .build(); + @Override + public > void setEntryReaderWriter( + ConfigEntry entry, + TweedEntryReader entryReader, + TweedEntryWriter entryWriter + ) { + CustomEntryData customEntryData = getOrCreateCustomEntryData(entry); + customEntryData.readerDefinition(entryReader); + customEntryData.writerDefinition(entryWriter); + } - try { - readWriteContextExtensionsDataPatchwork = - patchworkClassCreator.createClass(readWriteContextExtensionsDataClasses.keySet()); - for (PatchworkClassPart patchworkClassPart : readWriteContextExtensionsDataPatchwork.parts()) { - RegisteredExtensionDataImpl - registeredExtension - = readWriteContextExtensionsDataClasses.get(patchworkClassPart.partInterface()); - registeredExtension.setter = patchworkClassPart.fieldSetter(); - } - } catch (PatchworkClassGenerator.GenerationException e) { - throw new IllegalStateException( - "Failed to generate read write context extensions' data patchwork class", - e - ); - } + @Override + public > void setEntryReader(ConfigEntry entry, TweedEntryReader entryReader) { + getOrCreateCustomEntryData(entry).readerDefinition(entryReader); + } + + @Override + public > void setEntryWriter(ConfigEntry entry, TweedEntryWriter entryWriter) { + getOrCreateCustomEntryData(entry).writerDefinition(entryWriter); } @Override public void initEntry(ConfigEntry configEntry) { - TweedEntryReader baseReader; - TweedEntryWriter baseWriter; - if (configEntry.extensionsData().isPatchworkPartSet(EntryReaderWriterDefinition.class)) { - EntryReaderWriterDefinition rwDefintion = (EntryReaderWriterDefinition) configEntry.extensionsData(); - baseReader = rwDefintion.reader(); - baseWriter = rwDefintion.writer(); - } else { - baseReader = TweedEntryReaderWriterImpls.NOOP_READER_WRITER; - baseWriter = TweedEntryReaderWriterImpls.NOOP_READER_WRITER; + CustomEntryData customEntryData = getOrCreateCustomEntryData(configEntry); + customEntryData.readerChain(entryReaderMiddlewareContainer.process(customEntryData.readerDefinition())); + customEntryData.writerChain(entryWriterMiddlewareContainer.process(customEntryData.writerDefinition())); + } + + private CustomEntryData getOrCreateCustomEntryData(ConfigEntry entry) { + CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess); + if (entryData == null) { + entryData = new CustomEntryData(); + entry.extensionsData().set(customEntryDataAccess, entryData); } - - readWriteEntryDataExtension.set( - configEntry.extensionsData(), new ReadWriteEntryDataExtensionImpl( - entryReaderMiddlewareContainer.process(baseReader), - entryWriterMiddlewareContainer.process(baseWriter) - ) - ); + return entryData; } @Override - public void setEntryReaderWriterDefinition( - @NonNull ConfigEntry entry, - @NonNull EntryReaderWriterDefinition readerWriterDefinition - ) { - readerWriterDefinitionExtension.set(entry.extensionsData(), readerWriterDefinition); + public Patchwork createReadWriteContextExtensionsData() { + assert readWriteContextPatchworkFactory != null; + return readWriteContextPatchworkFactory.create(); } - @Override - public ReadWriteContextExtensionsData createReadWriteContextExtensionsData() { - try { - assert readWriteContextExtensionsDataPatchwork != null; - return (ReadWriteContextExtensionsData) readWriteContextExtensionsDataPatchwork.constructor().invoke(); - } catch (Throwable e) { - throw new IllegalStateException("Failed to instantiate read write context extensions' data", e); - } - } - - @Override public T read( - @NonNull TweedDataReader reader, - @NonNull ConfigEntry entry, - @NonNull ReadWriteContextExtensionsData contextExtensionsData + TweedDataReader reader, + ConfigEntry entry, + Patchwork contextExtensionsData ) throws TweedEntryReadException { try { - return getReaderChain(entry).read(reader, entry, new TweedReadWriteContextImpl(contextExtensionsData)); + return getReaderChain(entry).read(reader, entry, new TweedReadWriteContextImpl(this, contextExtensionsData)); } catch (TweedDataReadException e) { throw new TweedEntryReadException("Failed to read entry", e); } @@ -182,46 +135,35 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { @Override public void write( - @NonNull TweedDataVisitor writer, + TweedDataVisitor writer, @Nullable T value, - @NonNull ConfigEntry entry, - @NonNull ReadWriteContextExtensionsData contextExtensionsData + ConfigEntry entry, + Patchwork contextExtensionsData ) throws TweedEntryWriteException { try { - getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(contextExtensionsData)); + getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(this, contextExtensionsData)); } catch (TweedDataWriteException e) { throw new TweedEntryWriteException("Failed to write entry", e); } } - @Value - private static class ReadWriteEntryDataExtensionImpl implements ReadWriteEntryDataExtension { - TweedEntryReader entryReaderChain; - TweedEntryWriter entryWriterChain; + @Data + private static class CustomEntryData { + private TweedEntryReader readerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER; + private TweedEntryWriter writerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER; + private TweedEntryReader readerChain; + private TweedEntryWriter writerChain; } - @Setter - private static class RegisteredExtensionDataImpl, E> - implements RegisteredExtensionData { - private MethodHandle setter; - - @Override - public void set(U patchwork, E extension) { - try { - setter.invokeWithArguments(patchwork, extension); - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } - } - - static TweedEntryReader> getReaderChain(ConfigEntry elementEntry) { + @Override + public > TweedEntryReader getReaderChain(C entry) { //noinspection unchecked - return (TweedEntryReader>) ((ReadWriteEntryDataExtension) elementEntry.extensionsData()).entryReaderChain(); + return (TweedEntryReader) entry.extensionsData().get(customEntryDataAccess).readerChain(); } - static TweedEntryWriter> getWriterChain(ConfigEntry elementEntry) { + @Override + public > TweedEntryWriter getWriterChain(C entry) { //noinspection unchecked - return (TweedEntryWriter>) ((ReadWriteEntryDataExtension) elementEntry.extensionsData()).entryWriterChain(); + return (TweedEntryWriter) entry.extensionsData().get(customEntryDataAccess).writerChain(); } } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java index bcf0cbf..d0fea6c 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java @@ -10,7 +10,6 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Contract; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.util.*; @@ -63,7 +62,7 @@ public class TweedEntryReaderWriterImpls { } @RequiredArgsConstructor - private static class PrimitiveReaderWriter implements TweedEntryReaderWriter> { + private static class PrimitiveReaderWriter implements TweedEntryReaderWriter> { private final Function readerCall; private final BiConsumer writerCall; @@ -79,7 +78,7 @@ public class TweedEntryReaderWriterImpls { } } - public static class CollectionReaderWriter> implements TweedEntryReaderWriter> { + public static class CollectionReaderWriter> implements TweedEntryReaderWriter> { @Override public C read(TweedDataReader reader, CollectionConfigEntry entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException { assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start"); @@ -89,9 +88,9 @@ public class TweedEntryReaderWriterImpls { } ConfigEntry elementEntry = entry.elementEntry(); - TweedEntryReader> elementReader = ReadWriteExtensionImpl.getReaderChain(elementEntry); + TweedEntryReader> elementReader = context.readWriteExtension().getReaderChain(elementEntry); - List list = new ArrayList<>(20); + List<@Nullable T> list = new ArrayList<>(20); while (true) { token = reader.peekToken(); if (token.isListEnd()) { @@ -119,7 +118,7 @@ public class TweedEntryReaderWriterImpls { } ConfigEntry elementEntry = entry.elementEntry(); - TweedEntryWriter> elementWriter = ReadWriteExtensionImpl.getWriterChain(elementEntry); + TweedEntryWriter> elementWriter = context.readWriteExtension().getWriterChain(elementEntry); writer.visitListStart(); for (T element : value) { @@ -129,7 +128,7 @@ public class TweedEntryReaderWriterImpls { } } - public static class CompoundReaderWriter implements TweedEntryReaderWriter> { + public static class CompoundReaderWriter implements TweedEntryReaderWriter> { @Override public T read(TweedDataReader reader, CompoundConfigEntry entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException { assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start"); @@ -145,7 +144,7 @@ public class TweedEntryReaderWriterImpls { //noinspection unchecked ConfigEntry subEntry = (ConfigEntry) compoundEntries.get(key); - TweedEntryReader> subEntryReaderChain = ReadWriteExtensionImpl.getReaderChain(subEntry); + TweedEntryReader> subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry); Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context); entry.set(compoundValue, key, subEntryValue); } else { @@ -167,7 +166,7 @@ public class TweedEntryReaderWriterImpls { String key = e.getKey(); ConfigEntry subEntry = e.getValue(); - TweedEntryWriter> subEntryWriterChain = ReadWriteExtensionImpl.getWriterChain(subEntry); + TweedEntryWriter> subEntryWriterChain = context.readWriteExtension().getWriterChain(subEntry); writer.visitMapEntryKey(key); subEntryWriterChain.write(writer, entry.get(value, key), subEntry, context); diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedReadWriteContextImpl.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedReadWriteContextImpl.java index 45341a2..0fbc9de 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedReadWriteContextImpl.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedReadWriteContextImpl.java @@ -1,11 +1,13 @@ package de.siphalor.tweed5.data.extension.impl; +import de.siphalor.tweed5.data.extension.api.ReadWriteExtension; import de.siphalor.tweed5.data.extension.api.TweedReadContext; import de.siphalor.tweed5.data.extension.api.TweedWriteContext; -import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; +import de.siphalor.tweed5.patchwork.api.Patchwork; import lombok.Value; @Value public class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext { - ReadWriteContextExtensionsData extensionsData; + ReadWriteExtension readWriteExtension; + Patchwork extensionsData; } diff --git a/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java b/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java index 18b335d..7ac6519 100644 --- a/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java +++ b/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java @@ -1,23 +1,18 @@ package de.siphalor.tweed5.data.extension.impl; import de.siphalor.tweed5.core.api.container.ConfigContainer; -import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; +import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry; +import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; +import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; import de.siphalor.tweed5.core.impl.DefaultConfigContainer; import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; -import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition; 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.readwrite.TweedEntryReaderWriter; -import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters; import de.siphalor.tweed5.data.hjson.HjsonLexer; import de.siphalor.tweed5.data.hjson.HjsonReader; import de.siphalor.tweed5.data.hjson.HjsonWriter; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; -import lombok.RequiredArgsConstructor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,6 +22,8 @@ import java.io.Writer; import java.util.*; import java.util.function.Function; +import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter; +import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*; import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap; import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class ReadWriteExtensionImplTest { private final StringWriter stringWriter = new StringWriter(); private final ConfigContainer> configContainer = new DefaultConfigContainer<>(); - private StaticMapCompoundConfigEntryImpl> rootEntry; + private CompoundConfigEntry> rootEntry; @SuppressWarnings("unchecked") @BeforeEach @@ -44,14 +41,16 @@ class ReadWriteExtensionImplTest { configContainer.registerExtension(ReadWriteExtension.DEFAULT); configContainer.finishExtensionSetup(); - SimpleConfigEntryImpl intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class); - SimpleConfigEntryImpl booleanEntry = new SimpleConfigEntryImpl<>(configContainer, Boolean.class); - CollectionConfigEntryImpl> listEntry = new CollectionConfigEntryImpl<>( + SimpleConfigEntry intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class) + .apply(entryReaderWriter(intReaderWriter())); + + CollectionConfigEntry> listEntry = new CollectionConfigEntryImpl<>( configContainer, (Class>) (Class) List.class, ArrayList::new, - booleanEntry - ); + new SimpleConfigEntryImpl<>(configContainer, Boolean.class) + .apply(entryReaderWriter(booleanReaderWriter())) + ).apply(entryReaderWriter(collectionReaderWriter())); rootEntry = new StaticMapCompoundConfigEntryImpl<>( configContainer, @@ -61,16 +60,9 @@ class ReadWriteExtensionImplTest { entry("int", intEntry), entry("list", listEntry) )) - ); + ).apply(entryReaderWriter(compoundReaderWriter())); configContainer.attachTree(rootEntry); - - RegisteredExtensionData readerWriterData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class); - readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter())); - readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter())); - readerWriterData.set(listEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.collectionReaderWriter())); - readerWriterData.set(booleanEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.booleanReaderWriter())); - configContainer.initialize(); } @@ -128,19 +120,4 @@ class ReadWriteExtensionImplTest { private TweedDataVisitor setupWriter(Function writerFactory) { return writerFactory.apply(stringWriter); } - - @RequiredArgsConstructor - private static class TrivialEntryReaderWriterDefinition implements EntryReaderWriterDefinition { - private final TweedEntryReaderWriter readerWriter; - - @Override - public TweedEntryReader reader() { - return readerWriter; - } - - @Override - public TweedEntryWriter writer() { - return readerWriter; - } - } } diff --git a/tweed5-weaver-pojo-serde-extension/src/main/java/de/siphalor/tweed5/weaver/pojoext/serde/api/ReadWritePojoPostProcessor.java b/tweed5-weaver-pojo-serde-extension/src/main/java/de/siphalor/tweed5/weaver/pojoext/serde/api/ReadWritePojoPostProcessor.java index 21148a1..e871d29 100644 --- a/tweed5-weaver-pojo-serde-extension/src/main/java/de/siphalor/tweed5/weaver/pojoext/serde/api/ReadWritePojoPostProcessor.java +++ b/tweed5-weaver-pojo-serde-extension/src/main/java/de/siphalor/tweed5/weaver/pojoext/serde/api/ReadWritePojoPostProcessor.java @@ -74,49 +74,32 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor return; } - EntryReaderWriterDefinition definition = createDefinitionFromEntryConfig(entryConfig, context); - if (definition != null) { - readWriteExtension.setEntryReaderWriterDefinition(configEntry, definition); - } + //noinspection rawtypes,unchecked + readWriteExtension.setEntryReaderWriter( + (ConfigEntry) configEntry, + (TweedEntryReader) resolveReader(entryConfig, context), + (TweedEntryWriter) resolveWriter(entryConfig, context) + ); } - private @Nullable EntryReaderWriterDefinition createDefinitionFromEntryConfig(EntryReadWriteConfig entryConfig, WeavingContext context) { - String readerSpecText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader(); - String writerSpecText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer(); - - SerdePojoReaderWriterSpec readerSpec; - SerdePojoReaderWriterSpec writerSpec; - if (readerSpecText.equals(writerSpecText)) { - readerSpec = writerSpec = specFromText(readerSpecText, context); - } else { - readerSpec = specFromText(readerSpecText, context); - writerSpec = specFromText(writerSpecText, context); - } - - if (readerSpec == null && writerSpec == null) { - return null; - } + private TweedEntryReader resolveReader(EntryReadWriteConfig entryConfig, WeavingContext context) { + String specText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader(); + SerdePojoReaderWriterSpec spec = specFromText(specText, context); //noinspection unchecked,rawtypes - TweedEntryReader reader = Optional.ofNullable(readerSpec) - .map((spec) -> resolveReaderWriterFromSpec((Class>)(Object) TweedEntryReader.class, readerFactories, spec, context)) + return Optional.ofNullable(spec) + .map(s -> resolveReaderWriterFromSpec((Class>)(Object) TweedEntryReader.class, readerFactories, s, context)) .orElse(((TweedEntryReader) TweedEntryReaderWriterImpls.NOOP_READER_WRITER)); + } + + private TweedEntryWriter resolveWriter(EntryReadWriteConfig entryConfig, WeavingContext context) { + String specText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer(); + SerdePojoReaderWriterSpec spec = specFromText(specText, context); + //noinspection unchecked,rawtypes - TweedEntryWriter writer = Optional.ofNullable(writerSpec) - .map((spec) -> resolveReaderWriterFromSpec((Class>)(Object) TweedEntryWriter.class, writerFactories, spec, context)) + return Optional.ofNullable(spec) + .map(s -> resolveReaderWriterFromSpec((Class>)(Object) TweedEntryWriter.class, writerFactories, s, context)) .orElse(((TweedEntryWriter) TweedEntryReaderWriterImpls.NOOP_READER_WRITER)); - - return new EntryReaderWriterDefinition() { - @Override - public TweedEntryReader reader() { - return reader; - } - - @Override - public TweedEntryWriter writer() { - return writer; - } - }; } private @Nullable SerdePojoReaderWriterSpec specFromText(String specText, WeavingContext context) { 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 ec67708..c2b87ec 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 @@ -2,7 +2,8 @@ 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.api.extension.RegisteredExtensionData; +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.CollectionWeaving; import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry; @@ -24,24 +25,27 @@ public class CollectionPojoWeaver implements TweedPojoWeaver { .collectionEntryClass(CollectionConfigEntryImpl.class) .build(); - private RegisteredExtensionData weavingConfigAccess; + @Nullable + private PatchworkPartAccess weavingConfigAccess; @Override public void setup(SetupContext context) { this.weavingConfigAccess = context.registerWeavingContextExtensionData(CollectionWeavingConfig.class); } - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings({"unchecked"}) @Override public @Nullable ConfigEntry weaveEntry(ActualType valueType, WeavingContext context) { + assert weavingConfigAccess != null; + List> collectionTypeParams = valueType.getTypesOfSuperArguments(Collection.class); if (collectionTypeParams == null) { return null; } try { CollectionWeavingConfig weavingConfig = getOrCreateWeavingConfig(context); - WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy(); - weavingConfigAccess.set(newExtensionsData, weavingConfig); + Patchwork newExtensionsData = context.extensionsData().copy(); + newExtensionsData.set(weavingConfigAccess, weavingConfig); IntFunction> constructor = getCollectionConstructor(valueType); @@ -66,10 +70,10 @@ public class CollectionPojoWeaver implements TweedPojoWeaver { } private CollectionWeavingConfig getOrCreateWeavingConfig(WeavingContext context) { - CollectionWeavingConfig parent; - if (context.extensionsData().isPatchworkPartSet(CollectionWeavingConfig.class)) { - parent = (CollectionWeavingConfig) context.extensionsData(); - } else { + assert weavingConfigAccess != null; + + CollectionWeavingConfig parent = context.extensionsData().get(weavingConfigAccess); + if (parent == null) { parent = DEFAULT_WEAVING_CONFIG; } 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 633a8a5..9156864 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 @@ -2,10 +2,11 @@ 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.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.namingformat.api.NamingFormat; import de.siphalor.tweed5.namingformat.api.NamingFormatCollector; import de.siphalor.tweed5.namingformat.api.NamingFormats; +import de.siphalor.tweed5.patchwork.api.Patchwork; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry; import de.siphalor.tweed5.typeutils.api.type.ActualType; @@ -22,7 +23,6 @@ import java.lang.invoke.MethodHandle; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.List; -import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -37,7 +37,8 @@ public class CompoundPojoWeaver implements TweedPojoWeaver { .build(); private final NamingFormatCollector namingFormatCollector = new NamingFormatCollector(); - private RegisteredExtensionData weavingConfigAccess; + @Nullable + private PatchworkPartAccess weavingConfigAccess; public void setup(SetupContext context) { namingFormatCollector.setupFormats(); @@ -47,13 +48,15 @@ public class CompoundPojoWeaver implements TweedPojoWeaver { @Override public @Nullable ConfigEntry weaveEntry(ActualType valueType, WeavingContext context) { + assert weavingConfigAccess != null; + if (context.annotations().getAnnotation(CompoundWeaving.class) == null) { return null; } try { CompoundWeavingConfig weavingConfig = getOrCreateWeavingConfig(context); - WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy(); - weavingConfigAccess.set(newExtensionsData, weavingConfig); + Patchwork newExtensionsData = context.extensionsData().copy(); + newExtensionsData.set(weavingConfigAccess, weavingConfig); PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType()); @@ -69,10 +72,10 @@ public class CompoundPojoWeaver implements TweedPojoWeaver { } private CompoundWeavingConfig getOrCreateWeavingConfig(WeavingContext context) { - CompoundWeavingConfig parent; - if (context.extensionsData().isPatchworkPartSet(CompoundWeavingConfig.class)) { - parent = (CompoundWeavingConfig) context.extensionsData(); - } else { + assert weavingConfigAccess != null; + + CompoundWeavingConfig parent = context.extensionsData().get(weavingConfigAccess); + if (parent == null) { parent = DEFAULT_WEAVING_CONFIG; } @@ -143,10 +146,14 @@ public class CompoundPojoWeaver implements TweedPojoWeaver { private WeavableCompoundConfigEntry.SubEntry weaveCompoundSubEntry( PojoClassIntrospector.Property property, - WeavingContext.ExtensionsData newExtensionsData, + Patchwork newExtensionsData, WeavingContext parentContext ) { - String name = convertName(property.field().getName(), (CompoundWeavingConfig) newExtensionsData); + assert weavingConfigAccess != null; + CompoundWeavingConfig weavingConfig = newExtensionsData.get(weavingConfigAccess); + assert weavingConfig != null; + + String name = convertName(property.field().getName(), weavingConfig); WeavingContext subContext = createSubContextForProperty(property, name, newExtensionsData, parentContext); ConfigEntry subEntry; @@ -189,7 +196,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver { private WeavingContext createSubContextForProperty( PojoClassIntrospector.Property property, String name, - WeavingContext.ExtensionsData newExtensionsData, + Patchwork newExtensionsData, WeavingContext parentContext ) { return parentContext.subContextBuilder(name) diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/TweedPojoWeaver.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/TweedPojoWeaver.java index 0af0c10..ccee792 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/TweedPojoWeaver.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/TweedPojoWeaver.java @@ -1,7 +1,8 @@ package de.siphalor.tweed5.weaver.pojo.api.weaving; import de.siphalor.tweed5.construct.api.TweedConstructFactory; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; +import de.siphalor.tweed5.patchwork.api.PatchworkFactory; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; import org.jetbrains.annotations.ApiStatus; public interface TweedPojoWeaver extends TweedPojoWeavingFunction { @@ -11,6 +12,6 @@ public interface TweedPojoWeaver extends TweedPojoWeavingFunction { void setup(SetupContext context); interface SetupContext { - RegisteredExtensionData registerWeavingContextExtensionData(Class dataClass); + PatchworkPartAccess registerWeavingContextExtensionData(Class dataClass); } } diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/WeavingContext.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/WeavingContext.java index 11bd317..58ddecb 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/WeavingContext.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/api/weaving/WeavingContext.java @@ -18,7 +18,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull { TweedPojoWeavingFunction.NonNull weavingFunction; ConfigContainer configContainer; String[] path; - ExtensionsData extensionsData; + Patchwork extensionsData; AnnotatedElement annotations; public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer configContainer) { @@ -40,8 +40,6 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull { return weavingFunction.weaveEntry(valueType, context); } - public interface ExtensionsData extends Patchwork {} - @Accessors(fluent = true, chain = true) @Setter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @@ -51,7 +49,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull { private final TweedPojoWeavingFunction.NonNull weavingFunction; private final ConfigContainer configContainer; private final String[] path; - private ExtensionsData extensionsData; + private Patchwork extensionsData; private AnnotatedElement annotations; public WeavingContext build() { diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java index 9506d60..919f194 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java @@ -62,7 +62,7 @@ public class CollectionConfigEntryImpl> extends BaseC } @Override - public @NotNull T deepCopy(@NotNull T value) { + public @NotNull T deepCopy(T value) { T copy = instantiateCollection(value.size()); for (E element : value) { copy.add(elementEntry.deepCopy(element)); diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java index 043ae8e..cd912c4 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java @@ -7,6 +7,7 @@ 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.weaver.pojo.api.entry.WeavableCompoundConfigEntry; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.LinkedHashMap; @@ -97,7 +98,7 @@ public class StaticPojoCompoundConfigEntry extends BaseConfigEntry impleme } @Override - public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { + public void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) { if (visitor.enterCompoundEntry(this, value)) { subEntries.forEach((key, entry) -> { if (visitor.enterCompoundSubEntry(key)) { diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/TweedPojoWeaverBootstrapper.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/TweedPojoWeaverBootstrapper.java index 7fe61eb..ef04752 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/TweedPojoWeaverBootstrapper.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/weaving/TweedPojoWeaverBootstrapper.java @@ -2,22 +2,21 @@ package de.siphalor.tweed5.weaver.pojo.impl.weaving; import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; -import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClass; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator; -import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart; -import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving; +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.PojoWeaving; import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext; import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor; -import lombok.*; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.Nullable; import java.lang.annotation.Annotation; -import java.lang.invoke.MethodHandle; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; import java.util.stream.Collectors; /** @@ -31,7 +30,8 @@ public class TweedPojoWeaverBootstrapper { private final ConfigContainer configContainer; private final Collection weavers; private final Collection postProcessors; - private PatchworkClass contextExtensionsDataClass; + @Nullable + private PatchworkFactory contextExtensionsPatchworkFactory; public static TweedPojoWeaverBootstrapper create(Class pojoClass) { PojoWeaving rootWeavingConfig = expectAnnotation(pojoClass, PojoWeaving.class); @@ -88,44 +88,21 @@ public class TweedPojoWeaverBootstrapper { } private void setupWeavers() { - Map, RegisteredExtensionDataImpl> registeredExtensions = new HashMap<>(); + PatchworkFactory.Builder contextExtensionsPatchworkFactoryBuilder = PatchworkFactory.builder(); - TweedPojoWeaver.SetupContext setupContext = new TweedPojoWeaver.SetupContext() { - @Override - public RegisteredExtensionData registerWeavingContextExtensionData( - Class dataClass - ) { - RegisteredExtensionDataImpl registeredExtension = new RegisteredExtensionDataImpl<>(); - registeredExtensions.put(dataClass, registeredExtension); - return registeredExtension; - } - }; + TweedPojoWeaver.SetupContext setupContext = contextExtensionsPatchworkFactoryBuilder::registerPart; for (TweedPojoWeaver weaver : weavers) { weaver.setup(setupContext); } - PatchworkClassCreator weavingContextCreator = PatchworkClassCreator.builder() - .classPackage(this.getClass().getPackage().getName() + ".generated") - .classPrefix("WeavingContext$") - .patchworkInterface(WeavingContext.ExtensionsData.class) - .build(); - - try { - this.contextExtensionsDataClass = weavingContextCreator.createClass(registeredExtensions.keySet()); - - for (PatchworkClassPart part : this.contextExtensionsDataClass.parts()) { - RegisteredExtensionDataImpl registeredExtension = registeredExtensions.get(part.partInterface()); - registeredExtension.setter(part.fieldSetter()); - } - } catch (PatchworkClassGenerator.GenerationException e) { - throw new PojoWeavingException("Failed to create weaving context extensions data"); - } + contextExtensionsPatchworkFactory = contextExtensionsPatchworkFactoryBuilder.build(); } private WeavingContext createWeavingContext() { try { - WeavingContext.ExtensionsData extensionsData = (WeavingContext.ExtensionsData) contextExtensionsDataClass.constructor().invoke(); + assert contextExtensionsPatchworkFactory != null; + Patchwork extensionsData = contextExtensionsPatchworkFactory.create(); return WeavingContext.builder(this::weaveEntry, configContainer) .extensionsData(extensionsData) @@ -157,18 +134,4 @@ public class TweedPojoWeaverBootstrapper { } } } - - @Setter - private static class RegisteredExtensionDataImpl implements RegisteredExtensionData { - private MethodHandle setter; - - @Override - public void set(WeavingContext.ExtensionsData patchwork, E extension) { - try { - setter.invokeWithArguments(patchwork, extension); - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } - } } diff --git a/tweed5-weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaverTest.java b/tweed5-weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaverTest.java index 28785d1..6c66251 100644 --- a/tweed5-weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaverTest.java +++ b/tweed5-weaver-pojo/src/test/java/de/siphalor/tweed5/weaver/pojo/api/weaving/CompoundPojoWeaverTest.java @@ -2,24 +2,18 @@ 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.api.entry.SimpleConfigEntry; -import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; -import de.siphalor.tweed5.namingformat.api.NamingFormat; -import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; -import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry; +import de.siphalor.tweed5.patchwork.api.PatchworkFactory; import de.siphalor.tweed5.typeutils.api.type.ActualType; -import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig; -import lombok.AllArgsConstructor; +import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Test; import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith; import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; @SuppressWarnings("unused") @NullUnmarked @@ -27,13 +21,12 @@ class CompoundPojoWeaverTest { @Test void weave() { + PatchworkFactory.Builder weavingContextExtensionDataFactoryBuilder = PatchworkFactory.builder(); + CompoundPojoWeaver compoundWeaver = new CompoundPojoWeaver(); - compoundWeaver.setup(new TweedPojoWeaver.SetupContext() { - @Override - public RegisteredExtensionData registerWeavingContextExtensionData(Class dataClass) { - return (patchwork, extension) -> ((ExtensionsDataMock) patchwork).weavingConfig = (CompoundWeavingConfig) extension; - } - }); + compoundWeaver.setup(weavingContextExtensionDataFactoryBuilder::registerPart); + + PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build(); WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() { @Override @@ -46,7 +39,7 @@ class CompoundPojoWeaverTest { } } }, mock(ConfigContainer.class)) - .extensionsData(new ExtensionsDataMock(null)) + .extensionsData(weavingContextExtensionDataFactory.create()) .annotations(Compound.class) .build(); @@ -86,39 +79,4 @@ class CompoundPojoWeaverTest { public static class InnerValue { public Integer value; } - - @AllArgsConstructor - private static class ExtensionsDataMock implements WeavingContext.ExtensionsData, CompoundWeavingConfig { - private CompoundWeavingConfig weavingConfig; - - @Override - public boolean isPatchworkPartDefined(Class patchworkInterface) { - return patchworkInterface == CompoundWeavingConfig.class; - } - - @Override - public boolean isPatchworkPartSet(Class patchworkInterface) { - return weavingConfig != null; - } - - @Override - public WeavingContext.ExtensionsData copy() { - return new ExtensionsDataMock(weavingConfig); - } - - @Override - public NamingFormat compoundSourceNamingFormat() { - return weavingConfig.compoundSourceNamingFormat(); - } - - @Override - public NamingFormat compoundTargetNamingFormat() { - return weavingConfig.compoundTargetNamingFormat(); - } - - @Override - public @Nullable Class compoundEntryClass() { - return weavingConfig.compoundEntryClass(); - } - } }