From 59f882bd12c6678cd0598fbbc92c97a6e0f0e6f9 Mon Sep 17 00:00:00 2001 From: Siphalor Date: Fri, 25 Apr 2025 15:33:55 +0200 Subject: [PATCH] [*] Rework registration of TweedExtensions --- .../de.siphalor.tweed5.base-module.gradle.kts | 8 +- gradle/libs.versions.toml | 3 +- .../core/api/container/ConfigContainer.java | 13 +- .../core/api/extension/TweedExtension.java | 9 +- .../extension/TweedExtensionSetupContext.java | 5 +- .../core/impl/DefaultConfigContainer.java | 237 +++++++++++++---- .../core/impl/DefaultConfigContainerTest.java | 247 ++++++++++++++++++ .../comment/api/CommentExtension.java | 3 + .../comment/impl/CommentExtensionImpl.java | 25 +- .../pather/api/PathTracking.java | 6 +- .../pather/api/PatherExtension.java | 2 + .../pather/impl/PatherExtensionImpl.java | 13 +- .../validation/api/ValidationExtension.java | 3 + .../impl/ValidationExtensionImpl.java | 37 +-- .../api/ValidationFallbackExtension.java | 2 + .../impl/ValidationFallbackExtensionImpl.java | 9 +- .../impl/CommentExtensionImplTest.java | 49 ++-- .../impl/ValidationExtensionImplTest.java | 13 +- .../ValidationFallbackExtensionImplTest.java | 37 ++- .../impl/PatchworkClassGenerator.java | 13 +- .../extension/api/ReadWriteExtension.java | 3 + .../impl/ReadWriteExtensionImpl.java | 43 +-- .../impl/ReadWriteExtensionImplTest.java | 36 ++- .../serde/api/ReadWritePojoPostProcessor.java | 2 +- .../serde/WeaverPojoSerdeExtensionTest.java | 3 +- .../weaving/TweedPojoWeaverBootstrapper.java | 67 +---- .../api/weaving/CompoundPojoWeaverTest.java | 8 +- 27 files changed, 639 insertions(+), 257 deletions(-) create mode 100644 tweed5-core/src/test/java/de/siphalor/tweed5/core/impl/DefaultConfigContainerTest.java diff --git a/buildSrc/src/main/kotlin/de.siphalor.tweed5.base-module.gradle.kts b/buildSrc/src/main/kotlin/de.siphalor.tweed5.base-module.gradle.kts index 01ede73..44d40bc 100644 --- a/buildSrc/src/main/kotlin/de.siphalor.tweed5.base-module.gradle.kts +++ b/buildSrc/src/main/kotlin/de.siphalor.tweed5.base-module.gradle.kts @@ -11,8 +11,8 @@ group = rootProject.group version = rootProject.version java { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.main.get()) + targetCompatibility = JavaVersion.toVersion(libs.versions.java.main.get()) } repositories { @@ -52,6 +52,10 @@ dependencies { testImplementation(libs.assertj) } +tasks.compileTestJava { + sourceCompatibility = libs.versions.java.test.get() + targetCompatibility = libs.versions.java.test.get() +} tasks.test { dependsOn(testAgentClasspath) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3b417d..94671bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,8 @@ assertj = "3.26.3" asm = "9.7" autoservice = "1.1.1" -java = "8" +java-main = "8" +java-test = "21" jetbrains-annotations = "26.0.1" jspecify = "1.0.0" junit = "5.12.0" 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 8acb90c..ebca342 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 @@ -9,6 +9,7 @@ import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.Map; +import java.util.Optional; /** * The main wrapper for a config tree.
@@ -20,16 +21,14 @@ public interface ConfigContainer { @SuppressWarnings("rawtypes") TweedConstructFactory FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build(); - default void registerExtensions(TweedExtension... extensions) { - for (TweedExtension extension : extensions) { - registerExtension(extension); + default void registerExtensions(Class... extensionClasses) { + for (Class extensionClass : extensionClasses) { + registerExtension(extensionClass); } } + void registerExtension(Class extensionClass); - void registerExtension(TweedExtension extension); - - @Nullable - E extension(Class extensionClass); + Optional extension(Class extensionClass); Collection extensions(); void finishExtensionSetup(); diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtension.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtension.java index 9466143..256e9a6 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtension.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtension.java @@ -1,11 +1,18 @@ package de.siphalor.tweed5.core.api.extension; +import de.siphalor.tweed5.construct.api.TweedConstructFactory; +import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.entry.ConfigEntry; public interface TweedExtension { + TweedConstructFactory FACTORY = TweedConstructFactory.builder(TweedExtension.class) + .typedArg(ConfigContainer.class) + .typedArg(TweedExtensionSetupContext.class) + .build(); + String getId(); - default void setup(TweedExtensionSetupContext context) { + default void extensionsFinalized() { } default void initEntry(ConfigEntry configEntry) { 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 55b75d8..ac73be1 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,9 +1,6 @@ package de.siphalor.tweed5.core.api.extension; -import de.siphalor.tweed5.core.api.container.ConfigContainer; - public interface TweedExtensionSetupContext { - ConfigContainer configContainer(); RegisteredExtensionData registerEntryExtensionData(Class dataClass); - void registerExtension(TweedExtension extension); + 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 93e0f7a..23f493c 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,7 +3,10 @@ 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.*; +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; @@ -12,56 +15,34 @@ import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart; import de.siphalor.tweed5.utils.api.collection.InheritanceMap; import lombok.Getter; import lombok.Setter; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.*; -@NullUnmarked public class DefaultConfigContainer implements ConfigContainer { @Getter private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP; + private final Set> requestedExtensions = new HashSet<>(); private final InheritanceMap extensions = new InheritanceMap<>(TweedExtension.class); - private ConfigEntry rootEntry; - private PatchworkClass entryExtensionsDataPatchworkClass; - private Map, RegisteredExtensionDataImpl> registeredEntryDataExtensions; + private @Nullable ConfigEntry rootEntry; + private @Nullable PatchworkClass entryExtensionsDataPatchworkClass; + private final Map, RegisteredExtensionDataImpl> registeredEntryDataExtensions + = new HashMap<>(); @Override - public @Nullable E extension(Class extensionClass) { - try { - return extensions.getSingleInstance(extensionClass); - } catch (InheritanceMap.NonUniqueResultException e) { - return null; - } - } - - @Override - public Collection extensions() { - return Collections.unmodifiableCollection(extensions.values()); - } - - @Override - public void registerExtension(TweedExtension extension) { + public void registerExtension(Class extensionClass) { requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP); - - if (!extensions.putIfAbsent(extension)) { - throw new IllegalArgumentException("Extension " + extension.getClass().getName() + " is already registered"); - } + requestedExtensions.add(extensionClass); } @Override public void finishExtensionSetup() { requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP); - registeredEntryDataExtensions = new HashMap<>(); - Collection additionalExtensions = new ArrayList<>(); TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() { - @Override - public ConfigContainer configContainer() { - return DefaultConfigContainer.this; - } - @Override public RegisteredExtensionData registerEntryExtensionData(Class dataClass) { if (registeredEntryDataExtensions.containsKey(dataClass)) { @@ -73,24 +54,31 @@ public class DefaultConfigContainer implements ConfigContainer { } @Override - public void registerExtension(TweedExtension extension) { - if (!extensions.containsAnyInstanceForClass(extension.getClass())) { - additionalExtensions.add(extension); + public void registerExtension(Class extensionClass) { + if (!extensions.containsAnyInstanceForClass(extensionClass)) { + requestedExtensions.add(extensionClass); } } }; - Collection extensionsToSetup = extensions.values(); - while (!extensionsToSetup.isEmpty()) { - for (TweedExtension extension : extensionsToSetup) { - extension.setup(extensionSetupContext); - } + Set> abstractExtensionClasses = new HashSet<>(); - for (TweedExtension additionalExtension : additionalExtensions) { - extensions.putIfAbsent(additionalExtension); + while (true) { + if (!requestedExtensions.isEmpty()) { + Class extensionClass = popFromIterable(requestedExtensions); + if (isAbstractClass(extensionClass)) { + abstractExtensionClasses.add(extensionClass); + } else { + extensions.put(instantiateExtension(extensionClass, extensionSetupContext)); + } + } else if (!abstractExtensionClasses.isEmpty()) { + Class extensionClass = popFromIterable(abstractExtensionClasses); + if (!extensions.containsAnyInstanceForClass(extensionClass)) { + extensions.put(instantiateAbstractExtension(extensionClass, extensionSetupContext)); + } + } else { + break; } - extensionsToSetup = new ArrayList<>(additionalExtensions); - additionalExtensions.clear(); } PatchworkClassCreator entryExtensionsDataGenerator = PatchworkClassCreator.builder() @@ -109,16 +97,135 @@ public class DefaultConfigContainer implements ConfigContainer { } setupPhase = ConfigContainerSetupPhase.TREE_SETUP; + + extensions.values().forEach(TweedExtension::extensionsFinalized); + } + + private static T popFromIterable(Iterable iterable) { + Iterator iterator = iterable.iterator(); + T value = iterator.next(); + iterator.remove(); + return value; + } + + protected E instantiateAbstractExtension( + Class extensionClass, + TweedExtensionSetupContext setupContext + ) { + try { + Field defaultField = extensionClass.getDeclaredField("DEFAULT"); + if (defaultField.getType() != Class.class) { + throw new IllegalStateException(createAbstractExtensionInstantiationExceptionMessage( + extensionClass, + "DEFAULT field has incorrect class " + defaultField.getType().getName() + "." + )); + } + if ((defaultField.getModifiers() & Modifier.STATIC) == 0) { + throw new IllegalStateException(createAbstractExtensionInstantiationExceptionMessage( + extensionClass, + "DEFAULT field is not static." + )); + } + if ((defaultField.getModifiers() & Modifier.PUBLIC) == 0) { + throw new IllegalStateException(createAbstractExtensionInstantiationExceptionMessage( + extensionClass, + "DEFAULT field is not public." + )); + } + Class defaultClass = (Class) defaultField.get(null); + if (!extensionClass.isAssignableFrom(defaultClass)) { + throw new IllegalStateException(createAbstractExtensionInstantiationExceptionMessage( + extensionClass, + "DEFAULT field contains class " + defaultClass.getName() + ", but that class " + + "does not inherit from " + extensionClass.getName() + )); + } + //noinspection unchecked + return instantiateExtension((Class) defaultClass, setupContext); + } catch (NoSuchFieldException ignored) { + throw new IllegalStateException(createAbstractExtensionInstantiationExceptionMessage(extensionClass, null)); + } catch (IllegalAccessException e) { + throw new IllegalStateException( + createAbstractExtensionInstantiationExceptionMessage( + extensionClass, + "Couldn't access DEFAULT field." + ), + e + ); + } + } + + private String createAbstractExtensionInstantiationExceptionMessage( + Class extensionClass, + @Nullable String detail + ) { + StringBuilder sb = new StringBuilder(); + sb.append("Requested extension class ").append(extensionClass.getName()).append(" is "); + if (extensionClass.isInterface()) { + sb.append("an interface "); + } else { + sb.append("an abstract class "); + } + sb.append("and cannot be instantiated directly.\n"); + sb.append("As the extension developer you can declare a public static DEFAULT field containing the class of "); + sb.append("a default implementation.\n"); + sb.append("As a user you can try registering an implementation of the extension class directly."); + if (detail != null) { + sb.append("\n"); + sb.append(detail); + } + return sb.toString(); + } + + protected E instantiateExtension( + Class extensionClass, + TweedExtensionSetupContext setupContext + ) { + if (isAbstractClass(extensionClass)) { + throw new IllegalStateException( + "Cannot instantiate extension class " + extensionClass.getName() + " as it is abstract" + ); + } + return TweedExtension.FACTORY.construct(extensionClass) + .typedArg(TweedExtensionSetupContext.class, setupContext) + .typedArg(ConfigContainer.class, this) + .finish(); + } + + private boolean isAbstractClass(Class clazz) { + return clazz.isInterface() || (clazz.getModifiers() & Modifier.ABSTRACT) != 0; + } + + @Override + public Optional extension(Class extensionClass) { + requireSetupPhase( + ConfigContainerSetupPhase.TREE_SETUP, + ConfigContainerSetupPhase.TREE_SEALED, + ConfigContainerSetupPhase.READY + ); + try { + return Optional.ofNullable(extensions.getSingleInstance(extensionClass)); + } catch (InheritanceMap.NonUniqueResultException e) { + throw new IllegalStateException("Multiple extensions registered for class " + extensionClass.getName(), e); + } + } + + @Override + public Collection extensions() { + requireSetupPhase( + ConfigContainerSetupPhase.TREE_SETUP, + ConfigContainerSetupPhase.TREE_SEALED, + ConfigContainerSetupPhase.READY + ); + return Collections.unmodifiableCollection(extensions.values()); } @Override public void attachAndSealTree(ConfigEntry rootEntry) { requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP); - this.rootEntry = rootEntry; - finishEntrySetup(); - } - private void finishEntrySetup() { + this.rootEntry = rootEntry; + rootEntry.visitInOrder(entry -> { if (!entry.sealed()) { entry.seal(DefaultConfigContainer.this); @@ -133,6 +240,7 @@ public class DefaultConfigContainer implements ConfigContainer { 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); @@ -141,7 +249,11 @@ public class DefaultConfigContainer implements ConfigContainer { @Override public Map, ? extends RegisteredExtensionData> entryDataExtensions() { - requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED); + requireSetupPhase( + ConfigContainerSetupPhase.TREE_SETUP, + ConfigContainerSetupPhase.TREE_SEALED, + ConfigContainerSetupPhase.READY + ); return registeredEntryDataExtensions; } @@ -149,6 +261,7 @@ public class DefaultConfigContainer implements ConfigContainer { public void initialize() { requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED); + assert rootEntry != null; rootEntry.visitInOrder(entry -> { for (TweedExtension extension : extensions()) { extension.initEntry(entry); @@ -160,12 +273,32 @@ public class DefaultConfigContainer implements ConfigContainer { @Override public ConfigEntry rootEntry() { + requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED, ConfigContainerSetupPhase.READY); + + assert rootEntry != null; return rootEntry; } - private void requireSetupPhase(ConfigContainerSetupPhase required) { - if (setupPhase != required) { - throw new IllegalStateException("Config container is not in correct stage, expected " + required + ", but is in " + setupPhase); + private void requireSetupPhase(ConfigContainerSetupPhase... allowedPhases) { + for (ConfigContainerSetupPhase allowedPhase : allowedPhases) { + if (allowedPhase == setupPhase) { + return; + } + } + if (allowedPhases.length == 1) { + throw new IllegalStateException( + "Config container is not in correct phase, expected " + + allowedPhases[0] + + ", but is in " + + setupPhase + ); + } else { + throw new IllegalStateException( + "Config container is not in correct phase, expected any of " + + Arrays.toString(allowedPhases) + + ", but is in " + + setupPhase + ); } } 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 new file mode 100644 index 0000000..1a8bb8c --- /dev/null +++ b/tweed5-core/src/test/java/de/siphalor/tweed5/core/impl/DefaultConfigContainerTest.java @@ -0,0 +1,247 @@ +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; +import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DefaultConfigContainerTest { + + @Test + void extensionSetup() { + var configContainer = new DefaultConfigContainer<>(); + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.EXTENSIONS_SETUP); + + configContainer.registerExtension(ExtensionA.class); + configContainer.finishExtensionSetup(); + + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SETUP); + assertThat(configContainer.extensions()) + .satisfiesExactlyInAnyOrder( + extension -> assertThat(extension).isInstanceOf(ExtensionA.class), + extension -> assertThat(extension).isInstanceOf(ExtensionBImpl.class) + ); + } + + @Test + void extensionSetupDefaultOverride() { + var configContainer = new DefaultConfigContainer<>(); + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.EXTENSIONS_SETUP); + + configContainer.registerExtensions(ExtensionA.class, ExtensionBNonDefaultImpl.class); + configContainer.finishExtensionSetup(); + + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SETUP); + assertThat(configContainer.extensions()) + .satisfiesExactlyInAnyOrder( + extension -> assertThat(extension).isInstanceOf(ExtensionA.class), + extension -> assertThat(extension).isInstanceOf(ExtensionBNonDefaultImpl.class) + ); + } + + @Test + void extensionSetupMissingDefault() { + var configContainer = new DefaultConfigContainer<>(); + configContainer.registerExtension(ExtensionMissingDefault.class); + assertThatThrownBy(configContainer::finishExtensionSetup).hasMessageContaining("ExtensionMissingDefault"); + } + + @Test + void extensionSetupAbstractDefault() { + var configContainer = new DefaultConfigContainer<>(); + configContainer.registerExtension(ExtensionSelfReferencingDefault.class); + assertThatThrownBy(configContainer::finishExtensionSetup).hasMessageContaining("ExtensionSelfReferencingDefault"); + } + + @Test + void extensionSetupWrongDefault() { + var configContainer = new DefaultConfigContainer<>(); + configContainer.registerExtension(ExtensionWrongDefault.class); + assertThatThrownBy(configContainer::finishExtensionSetup) + .hasMessageContaining("ExtensionWrongDefault", "ExtensionBImpl"); + } + + @Test + void extensionSetupNonStaticDefault() { + var configContainer = new DefaultConfigContainer<>(); + configContainer.registerExtension(ExtensionNonStaticDefault.class); + assertThatThrownBy(configContainer::finishExtensionSetup) + .hasMessageContaining("ExtensionNonStaticDefault", "static"); + } + + @Test + void extensionSetupNonPublicDefault() { + var configContainer = new DefaultConfigContainer<>(); + configContainer.registerExtension(ExtensionNonPublicDefault.class); + assertThatThrownBy(configContainer::finishExtensionSetup) + .hasMessageContaining("ExtensionNonPublicDefault", "public"); + } + + @Test + void extension() { + var configContainer = new DefaultConfigContainer<>(); + configContainer.registerExtensions(ExtensionA.class, ExtensionB.class); + configContainer.finishExtensionSetup(); + + assertThat(configContainer.extension(ExtensionA.class)).containsInstanceOf(ExtensionA.class); + assertThat(configContainer.extension(ExtensionB.class)).containsInstanceOf(ExtensionBImpl.class); + assertThat(configContainer.extension(ExtensionBImpl.class)).containsInstanceOf(ExtensionBImpl.class); + assertThat(configContainer.extension(ExtensionBNonDefaultImpl.class)).isEmpty(); + } + + @SuppressWarnings("unchecked") + @Test + void attachAndSealTree() { + var configContainer = new DefaultConfigContainer>(); + var compoundEntry = new StaticMapCompoundConfigEntryImpl<>( + (Class>)(Class) Map.class, + (capacity) -> new HashMap<>(capacity * 2, 0.5F) + ); + var subEntry = new SimpleConfigEntryImpl<>(String.class); + compoundEntry.addSubEntry("test", subEntry); + + configContainer.finishExtensionSetup(); + + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SETUP); + configContainer.attachAndSealTree(compoundEntry); + + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SEALED); + assertThat(compoundEntry.sealed()).isTrue(); + assertThat(subEntry.sealed()).isTrue(); + assertThat(configContainer.rootEntry()).isSameAs(compoundEntry); + } + + @Test + void createExtensionsData() { + var configContainer = new DefaultConfigContainer<>(); + 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"); + } + + @Test + void initialize() { + var configContainer = new DefaultConfigContainer>(); + var compoundEntry = new StaticMapCompoundConfigEntryImpl<>( + (Class>)(Class) Map.class, + (capacity) -> new HashMap<>(capacity * 2, 0.5F) + ); + var subEntry = new SimpleConfigEntryImpl<>(String.class); + compoundEntry.addSubEntry("test", subEntry); + + configContainer.registerExtension(ExtensionInitTracker.class); + configContainer.finishExtensionSetup(); + configContainer.attachAndSealTree(compoundEntry); + + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SEALED); + configContainer.initialize(); + assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.READY); + + var initTracker = configContainer.extension(ExtensionInitTracker.class).orElseThrow(); + assertThat(initTracker.initializedEntries()).containsExactlyInAnyOrder(compoundEntry, subEntry); + } + + public static class ExtensionA implements TweedExtension { + public ExtensionA(TweedExtensionSetupContext context) { + context.registerExtension(ExtensionB.class); + } + + @Override + public String getId() { + return "a"; + } + } + + public interface ExtensionB extends TweedExtension { + Class DEFAULT = ExtensionBImpl.class; + } + public static class ExtensionBImpl implements ExtensionB { + public ExtensionBImpl(TweedExtensionSetupContext context) { + context.registerEntryExtensionData(ExtensionBData.class); + } + + @Override + public String getId() { + return "b"; + } + } + public static class ExtensionBNonDefaultImpl implements ExtensionB { + @Override + public String getId() { + return "b-non-default"; + } + } + public interface ExtensionBData { + String test(); + } + record ExtensionBDataImpl(String test) implements ExtensionBData { } + + @Getter + public static class ExtensionInitTracker implements TweedExtension { + private final Collection> initializedEntries = new ArrayList<>(); + + @Override + public String getId() { + return "init-tracker"; + } + + @Override + public void initEntry(ConfigEntry configEntry) { + initializedEntries.add(configEntry); + } + } + + public interface ExtensionMissingDefault extends TweedExtension {} + + public interface ExtensionSelfReferencingDefault extends TweedExtension { + Class DEFAULT = ExtensionSelfReferencingDefault.class; + } + + public interface ExtensionWrongDefault extends TweedExtension { + Class DEFAULT = ExtensionBImpl.class; + } + + public static abstract class ExtensionNonStaticDefault implements TweedExtension { + public final Class DEFAULT = ExtensionNonStaticDefault.class; + } + + public static abstract class ExtensionNonPublicDefault implements TweedExtension { + protected static final Class DEFAULT = ExtensionNonPublicDefault.class; + } +} 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 030d6b1..2bac99b 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 @@ -2,8 +2,11 @@ package de.siphalor.tweed5.defaultextensions.comment.api; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.extension.TweedExtension; +import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl; import org.jspecify.annotations.Nullable; public interface CommentExtension extends TweedExtension { + Class DEFAULT = CommentExtensionImpl.class; + @Nullable String getFullComment(ConfigEntry configEntry); } 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 8f6effe..db99ad6 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 @@ -1,6 +1,7 @@ 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.entry.ConfigEntry; import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; @@ -14,15 +15,21 @@ import de.siphalor.tweed5.defaultextensions.comment.api.*; import lombok.Getter; import lombok.Value; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; @AutoService(CommentExtension.class) -@NullUnmarked public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension { + private final ConfigContainer configContainer; @Getter - private RegisteredExtensionData internalEntryDataExtension; - private DefaultMiddlewareContainer middlewareContainer; + private final RegisteredExtensionData internalEntryDataExtension; + private final DefaultMiddlewareContainer middlewareContainer; + + public CommentExtensionImpl(ConfigContainer configContainer, TweedExtensionSetupContext context) { + this.configContainer = configContainer; + this.internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class); + context.registerEntryExtensionData(EntryComment.class); + this.middlewareContainer = new DefaultMiddlewareContainer<>(); + } @Override public String getId() { @@ -30,18 +37,12 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE } @Override - public void setup(TweedExtensionSetupContext context) { - internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class); - context.registerEntryExtensionData(EntryComment.class); - - middlewareContainer = new DefaultMiddlewareContainer<>(); - - for (TweedExtension extension : context.configContainer().extensions()) { + public void extensionsFinalized() { + for (TweedExtension extension : configContainer.extensions()) { if (extension instanceof CommentModifyingExtension) { middlewareContainer.register(((CommentModifyingExtension) extension).commentMiddleware()); } } - middlewareContainer.seal(); } 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 fccf0ff..318a3ad 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 @@ -32,8 +32,10 @@ public class PathTracking implements PatherData { } public void popPathPart() { - String poppedPart = pathParts.pop(); - pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1); + if (!pathParts.isEmpty()) { + String poppedPart = pathParts.pop(); + pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1); + } } public void pushListContext() { 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 382f808..59f871f 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,6 +1,8 @@ package de.siphalor.tweed5.defaultextensions.pather.api; import de.siphalor.tweed5.core.api.extension.TweedExtension; +import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl; public interface PatherExtension extends TweedExtension { + Class DEFAULT = PatherExtensionImpl.class; } 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 da13592..8765528 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 @@ -11,10 +11,7 @@ import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupCo 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.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.defaultextensions.pather.api.*; import lombok.val; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullUnmarked; @@ -25,7 +22,7 @@ import org.jspecify.annotations.Nullable; public class PatherExtensionImpl implements PatherExtension, TweedExtension, ReadWriteRelatedExtension { private static final String PATHER_ID = "pather"; - private RegisteredExtensionData rwContextPathTrackingData; + private RegisteredExtensionData rwContextPathTrackingData; private Middleware> entryReaderMiddleware; private Middleware> entryWriterMiddleware; @@ -36,7 +33,7 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea @Override public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) { - rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PathTracking.class); + rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PatherData.class); entryReaderMiddleware = createEntryReaderMiddleware(); entryWriterMiddleware = createEntryWriterMiddleware(); @@ -55,7 +52,7 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea val castedInner = (TweedEntryReader>) inner; return (TweedDataReader reader, ConfigEntry entry, TweedReadContext context) -> { - if (context.extensionsData().isPatchworkPartSet(PathTracking.class)) { + if (context.extensionsData().isPatchworkPartSet(PatherData.class)) { return castedInner.read(reader, entry, context); } @@ -80,7 +77,7 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea val castedInner = (TweedEntryWriter>) inner; return (TweedDataVisitor writer, Object value, ConfigEntry entry, TweedWriteContext context) -> { - if (context.extensionsData().isPatchworkPartSet(PathTracking.class)) { + if (context.extensionsData().isPatchworkPartSet(PatherData.class)) { castedInner.write(writer, value, entry, context); return; } 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 d9347b1..df80db3 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 @@ -3,8 +3,11 @@ 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.defaultextensions.validation.api.result.ValidationIssues; +import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl; import org.jspecify.annotations.Nullable; public interface ValidationExtension extends TweedExtension { + Class DEFAULT = ValidationExtensionImpl.class; + ValidationIssues validate(ConfigEntry entry, T value); } 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 26ef7d6..8bb7081 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 @@ -1,6 +1,7 @@ package de.siphalor.tweed5.defaultextensions.validation.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.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; @@ -22,26 +23,24 @@ 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.impl.PatherExtensionImpl; +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.ValidationIssues; 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 org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.*; @AutoService(ValidationExtension.class) -@NullUnmarked public class ValidationExtensionImpl implements ReadWriteRelatedExtension, ValidationExtension, CommentModifyingExtension { private static final ValidationResult PRIMITIVE_IS_NULL_RESULT = ValidationResult.withIssues( null, @@ -74,9 +73,19 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid } }; - private RegisteredExtensionData validationEntryDataExtension; - private MiddlewareContainer entryValidatorMiddlewareContainer; - private RegisteredExtensionData readContextValidationIssuesExtensionData; + private final ConfigContainer configContainer; + private final RegisteredExtensionData validationEntryDataExtension; + private final MiddlewareContainer entryValidatorMiddlewareContainer + = new DefaultMiddlewareContainer<>(); + private @Nullable RegisteredExtensionData + readContextValidationIssuesExtensionData; + + public ValidationExtensionImpl(ConfigContainer configContainer, TweedExtensionSetupContext context) { + this.configContainer = configContainer; + this.validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class); + context.registerEntryExtensionData(EntrySpecificValidation.class); + context.registerExtension(PatherExtension.class); + } @Override public String getId() { @@ -84,16 +93,12 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid } @Override - public void setup(TweedExtensionSetupContext context) { - context.registerExtension(new PatherExtensionImpl()); - - validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class); - context.registerEntryExtensionData(EntrySpecificValidation.class); - - entryValidatorMiddlewareContainer = new DefaultMiddlewareContainer<>(); - for (TweedExtension extension : context.configContainer().extensions()) { + public void extensionsFinalized() { + for (TweedExtension extension : configContainer.extensions()) { if (extension instanceof ValidationProvidingExtension) { - entryValidatorMiddlewareContainer.register(((ValidationProvidingExtension) extension).validationMiddleware()); + entryValidatorMiddlewareContainer.register( + ((ValidationProvidingExtension) extension).validationMiddleware() + ); } } entryValidatorMiddlewareContainer.seal(); 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 f7a695d..be1efc8 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,6 +1,8 @@ package de.siphalor.tweed5.defaultextensions.validationfallback.api; import de.siphalor.tweed5.core.api.extension.TweedExtension; +import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl; public interface ValidationFallbackExtension extends TweedExtension { + Class DEFAULT = ValidationFallbackExtensionImpl.class; } 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 52de7d0..1992cce 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 @@ -20,14 +20,13 @@ import java.util.stream.Collectors; @AutoService(ValidationFallbackExtension.class) public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension { - @Override - public String getId() { - return "validation-fallback"; + public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) { + context.registerEntryExtensionData(ValidationFallbackValue.class); } @Override - public void setup(TweedExtensionSetupContext context) { - context.registerEntryExtensionData(ValidationFallbackValue.class); + public String getId() { + return "validation-fallback"; } @Override 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 770381d..72c6a83 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 @@ -13,37 +13,40 @@ 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.extension.impl.ReadWriteExtensionImpl; 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.NullUnmarked; import org.junit.jupiter.api.Test; import java.io.StringWriter; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; +@NullUnmarked class CommentExtensionImplTest { private DefaultConfigContainer> configContainer; - private CommentExtension commentExtension; private StaticMapCompoundConfigEntryImpl> rootEntry; private SimpleConfigEntryImpl intEntry; private SimpleConfigEntryImpl stringEntry; private SimpleConfigEntryImpl noCommentEntry; - void setupContainer(Collection extraExtensions) { + @SafeVarargs + final void setupContainer(Class... extraExtensions) { configContainer = new DefaultConfigContainer<>(); - commentExtension = new CommentExtensionImpl(); - configContainer.registerExtension(commentExtension); - extraExtensions.forEach(configContainer::registerExtension); + configContainer.registerExtension(CommentExtension.DEFAULT); + configContainer.registerExtensions(extraExtensions); configContainer.finishExtensionSetup(); //noinspection unchecked @@ -68,9 +71,10 @@ class CommentExtensionImplTest { @Test void simpleComments() { - setupContainer(Collections.emptyList()); + setupContainer(); configContainer.initialize(); + CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow(); assertEquals("It is an integer", commentExtension.getFullComment(intEntry)); assertEquals("It is a string", commentExtension.getFullComment(stringEntry)); assertNull(commentExtension.getFullComment(noCommentEntry)); @@ -78,9 +82,10 @@ class CommentExtensionImplTest { @Test void commentProvidingExtension() { - setupContainer(Collections.singletonList(new TestCommentModifyingExtension())); + setupContainer(TestCommentModifyingExtension.class); configContainer.initialize(); + CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow(); assertEquals("The comment is:\nIt is an integer\nEND", commentExtension.getFullComment(intEntry)); assertEquals("The comment is:\nIt is a string\nEND", commentExtension.getFullComment(stringEntry)); assertEquals("The comment is:\n\nEND", commentExtension.getFullComment(noCommentEntry)); @@ -88,8 +93,7 @@ class CommentExtensionImplTest { @Test void simpleCommentsInHjson() { - ReadWriteExtension readWriteExtension = new ReadWriteExtensionImpl(); - setupContainer(Collections.singletonList(readWriteExtension)); + setupContainer(ReadWriteExtension.DEFAULT); setupReadWriteTypes(); configContainer.initialize(); @@ -98,6 +102,7 @@ class CommentExtensionImplTest { value.put("string", "Hello World"); value.put("noComment", 567L); + ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); StringWriter output = new StringWriter(); assertDoesNotThrow(() -> readWriteExtension.write( new HjsonWriter(output, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)), @@ -106,14 +111,23 @@ class CommentExtensionImplTest { readWriteExtension.createReadWriteContextExtensionsData() )); - assertEquals("// This is the root value.\n// It is the topmost value in the tree.\n" + - "{\n\t// It is an integer\n\tint: 123\n\t// It is a string\n" + - "\tstring: Hello World\n\tnoComment: 567\n}\n", output.toString()); + assertEquals( + """ + // This is the root value. + // It is the topmost value in the tree. + { + \t// It is an integer + \tint: 123 + \t// It is a string + \tstring: Hello World + \tnoComment: 567 + } + """, output.toString()); } private void setupReadWriteTypes() { //noinspection unchecked - RegisteredExtensionData readerWriterData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class); + var readerWriterData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class); readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter())); readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter())); @@ -126,7 +140,8 @@ class CommentExtensionImplTest { String comment; } - private static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension { + @NoArgsConstructor + public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension { @Override public String getId() { return "test-extension"; @@ -162,4 +177,4 @@ class CommentExtensionImplTest { return readerWriter; } } -} \ No newline at end of file +} 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 e8dd1b6..7f75218 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 @@ -8,7 +8,6 @@ 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.comment.impl.CommentExtensionImpl; 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; @@ -30,8 +29,6 @@ import static org.junit.jupiter.api.Assertions.*; class ValidationExtensionImplTest { private DefaultConfigContainer> configContainer; - private CommentExtension commentExtension; - private ValidationExtension validationExtension; private StaticMapCompoundConfigEntryImpl> rootEntry; private SimpleConfigEntryImpl byteEntry; private SimpleConfigEntryImpl intEntry; @@ -41,10 +38,8 @@ class ValidationExtensionImplTest { void setUp() { configContainer = new DefaultConfigContainer<>(); - commentExtension = new CommentExtensionImpl(); - configContainer.registerExtension(commentExtension); - validationExtension = new ValidationExtensionImpl(); - configContainer.registerExtension(validationExtension); + configContainer.registerExtension(CommentExtension.DEFAULT); + configContainer.registerExtension(ValidationExtension.DEFAULT); configContainer.finishExtensionSetup(); //noinspection unchecked @@ -90,6 +85,7 @@ class ValidationExtensionImplTest { value.put("int", i); value.put("double", d); + ValidationExtension validationExtension = configContainer.extension(ValidationExtension.class).orElseThrow(); ValidationIssues result = validationExtension.validate(rootEntry, value); assertNotNull(result); assertNotNull(result.issuesByPath()); @@ -103,6 +99,7 @@ class ValidationExtensionImplTest { value.put("int", 124); value.put("double", 0.2); + ValidationExtension validationExtension = configContainer.extension(ValidationExtension.class).orElseThrow(); ValidationIssues result = validationExtension.validate(rootEntry, value); assertNotNull(result); assertNotNull(result.issuesByPath()); @@ -127,4 +124,4 @@ class ValidationExtensionImplTest { assertEquals(expectedIssue, entryIssues.issues().iterator().next(), "Issue must match"); } -} \ No newline at end of file +} 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 c1cb2f2..a4778cd 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 @@ -10,13 +10,11 @@ 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.extension.impl.ReadWriteExtensionImpl; 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.comment.impl.CommentExtensionImpl; import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator; import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; @@ -24,10 +22,10 @@ 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.validation.api.validators.SimpleValidatorMiddleware; -import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl; 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; import org.junit.jupiter.params.ParameterizedTest; @@ -44,24 +42,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class ValidationFallbackExtensionImplTest { private DefaultConfigContainer configContainer; - private CommentExtension commentExtension; - private ValidationExtension validationExtension; - private ValidationFallbackExtension validationFallbackExtension; - private ReadWriteExtension readWriteExtension; private SimpleConfigEntryImpl intEntry; @SuppressWarnings("unchecked") @BeforeEach void setUp() { configContainer = new DefaultConfigContainer<>(); - commentExtension = new CommentExtensionImpl(); - configContainer.registerExtension(commentExtension); - validationExtension = new ValidationExtensionImpl(); - configContainer.registerExtension(validationExtension); - validationFallbackExtension = new ValidationFallbackExtensionImpl(); - configContainer.registerExtension(validationFallbackExtension); - readWriteExtension = new ReadWriteExtensionImpl(); - configContainer.registerExtension(readWriteExtension); + configContainer.registerExtension(CommentExtension.DEFAULT); + configContainer.registerExtension(ValidationExtension.DEFAULT); + configContainer.registerExtension(ValidationFallbackExtension.DEFAULT); + configContainer.registerExtension(ReadWriteExtension.DEFAULT); configContainer.finishExtensionSetup(); @@ -73,7 +63,7 @@ class ValidationFallbackExtensionImplTest { entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList( new SimpleValidatorMiddleware("non-null", new ConfigEntryValidator() { @Override - public ValidationResult validate(ConfigEntry configEntry, T value) { + 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) @@ -89,7 +79,7 @@ class ValidationFallbackExtensionImplTest { }), new SimpleValidatorMiddleware("range", new ConfigEntryValidator() { @Override - public ValidationResult validate(ConfigEntry configEntry, T value) { + 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))); @@ -134,6 +124,7 @@ class ValidationFallbackExtensionImplTest { @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, @@ -144,6 +135,7 @@ class ValidationFallbackExtensionImplTest { @Test void description() { + ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); StringWriter stringWriter = new StringWriter(); assertDoesNotThrow(() -> readWriteExtension.write( new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)), @@ -152,6 +144,13 @@ class ValidationFallbackExtensionImplTest { readWriteExtension.createReadWriteContextExtensionsData() )); - assertEquals("// Must not be null.\n// Must be between 1 and 6\n// \n// Default/Fallback value: 3\n5\n", stringWriter.toString()); + assertEquals( + """ + // Must not be null. + // Must be between 1 and 6 + //\s + // Default/Fallback value: 3 + 5 + """, stringWriter.toString()); } -} \ No newline at end of file +} 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 index f272541..3e2011c 100644 --- 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 @@ -22,9 +22,6 @@ public class PatchworkClassGenerator { * Class version to use (Java 8) */ private static final int CLASS_VERSION = Opcodes.V1_8; - private static final String TARGET_PACKAGE = "de.siphalor.tweed5.core.generated.contextextensions"; - private static final List DEFAULT_PATHWORK_INTERFACES - = Collections.singletonList(Type.getType(Patchwork.class)); private static final String INNER_EQUALS_METHOD_NAME = "patchwork$innerEquals"; @@ -609,6 +606,11 @@ public class PatchworkClassGenerator { super("Invalid patchwork part class " + partClass.getName() + ": " + message); this.partClass = partClass; } + + @Override + public String toString() { + return super.toString(); + } } @Value @@ -636,6 +638,11 @@ public class PatchworkClassGenerator { stringBuilder.append("\n"); return stringBuilder.toString(); } + + @Override + public String toString() { + return super.toString(); + } } public static class GenerationException extends Exception { 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 29e02af..25ff1e0 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 @@ -3,11 +3,14 @@ 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.impl.ReadWriteExtensionImpl; import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; import org.jspecify.annotations.Nullable; public interface ReadWriteExtension extends TweedExtension { + Class DEFAULT = ReadWriteExtensionImpl.class; + void setEntryReaderWriterDefinition(ConfigEntry entry, EntryReaderWriterDefinition readerWriterDefinition); ReadWriteContextExtensionsData createReadWriteContextExtensionsData(); 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 fe7f4af..3379e91 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 @@ -1,6 +1,7 @@ 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; @@ -24,7 +25,6 @@ import lombok.Setter; import lombok.Value; import lombok.val; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.lang.invoke.MethodHandle; @@ -33,16 +33,27 @@ import java.util.HashMap; import java.util.Map; @AutoService(ReadWriteExtension.class) -@NullUnmarked public class ReadWriteExtensionImpl implements ReadWriteExtension { + private final ConfigContainer configContainer; + private final RegisteredExtensionData + readerWriterDefinitionExtension; + private final RegisteredExtensionData readWriteEntryDataExtension; + private DefaultMiddlewareContainer> + entryReaderMiddlewareContainer + = new DefaultMiddlewareContainer<>(); + private DefaultMiddlewareContainer> + entryWriterMiddlewareContainer + = new DefaultMiddlewareContainer<>(); + private final Map, RegisteredExtensionDataImpl> + readWriteContextExtensionsDataClasses + = new HashMap<>(); + private @Nullable PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork; - private RegisteredExtensionData readerWriterDefinitionExtension; - private RegisteredExtensionData readWriteEntryDataExtension; - private DefaultMiddlewareContainer> entryReaderMiddlewareContainer; - private DefaultMiddlewareContainer> entryWriterMiddlewareContainer; - private Map, RegisteredExtensionDataImpl> - readWriteContextExtensionsDataClasses; - private PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork; + public ReadWriteExtensionImpl(ConfigContainer configContainer, TweedExtensionSetupContext context) { + this.configContainer = configContainer; + this.readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class); + this.readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class); + } @Override public String getId() { @@ -50,13 +61,8 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { } @Override - public void setup(TweedExtensionSetupContext context) { - readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class); - readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class); - - Collection extensions = context.configContainer().extensions(); - - readWriteContextExtensionsDataClasses = new HashMap<>(extensions.size()); + public void extensionsFinalized() { + Collection extensions = configContainer.extensions(); ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() { @Override @@ -154,6 +160,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { @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); @@ -161,7 +168,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { } @Override - public T read( + public T read( @NonNull TweedDataReader reader, @NonNull ConfigEntry entry, @NonNull ReadWriteContextExtensionsData contextExtensionsData @@ -174,7 +181,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { } @Override - public void write( + public void write( @NonNull TweedDataVisitor writer, @Nullable T value, @NonNull ConfigEntry entry, 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 3cefcbc..31832d9 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 @@ -27,21 +27,19 @@ import java.io.Writer; import java.util.*; import java.util.function.Function; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; class ReadWriteExtensionImplTest { - private StringWriter stringWriter; + private final StringWriter stringWriter = new StringWriter(); + private final ConfigContainer> configContainer = new DefaultConfigContainer<>(); private StaticMapCompoundConfigEntryImpl> rootEntry; - private ReadWriteExtension readWriteExtension; @SuppressWarnings("unchecked") @BeforeEach void setUp() { - ConfigContainer> configContainer = new DefaultConfigContainer<>(); - - readWriteExtension = new ReadWriteExtensionImpl(); - configContainer.registerExtension(readWriteExtension); + configContainer.registerExtension(ReadWriteExtension.DEFAULT); configContainer.finishExtensionSetup(); rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class>) (Class) Map.class), LinkedHashMap::new); @@ -73,6 +71,7 @@ class ReadWriteExtensionImplTest { value.put("int", 123); value.put("list", Arrays.asList(true, false, true)); + ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); assertDoesNotThrow(() -> readWriteExtension.write( setupWriter(writer -> new HjsonWriter(writer, new HjsonWriter.Options())), value, @@ -80,13 +79,33 @@ class ReadWriteExtensionImplTest { readWriteExtension.createReadWriteContextExtensionsData() )); - assertEquals("{\n\tint: 123\n\tlist: [\n\t\ttrue\n\t\tfalse\n\t\ttrue\n\t]\n}\n", stringWriter.toString()); + assertThat(stringWriter.toString()).isEqualTo(""" + { + \tint: 123 + \tlist: [ + \t\ttrue + \t\tfalse + \t\ttrue + \t] + } + """ + ); } @Test void read() { + ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); Map result = assertDoesNotThrow(() -> readWriteExtension.read( - new HjsonReader(new HjsonLexer(new StringReader("{\n\tint: 123\n\tlist: [\n\t\ttrue\n\t\tfalse\n\t\ttrue\n\t]\n}\n"))), + new HjsonReader(new HjsonLexer(new StringReader(""" + { + \tint: 123 + \tlist: [ + \t\ttrue + \t\tfalse + \t\ttrue + \t] + } + """))), rootEntry, readWriteExtension.createReadWriteContextExtensionsData() )); @@ -97,7 +116,6 @@ class ReadWriteExtensionImplTest { } private TweedDataVisitor setupWriter(Function writerFactory) { - stringWriter = new StringWriter(); return writerFactory.apply(stringWriter); } 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 94d3257..21148a1 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 @@ -68,7 +68,7 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor return; } - ReadWriteExtension readWriteExtension = context.configContainer().extension(ReadWriteExtension.class); + ReadWriteExtension readWriteExtension = context.configContainer().extension(ReadWriteExtension.class).orElse(null); if (readWriteExtension == null) { log.error("You must not use {} without the {}", this.getClass().getSimpleName(), ReadWriteExtension.class.getSimpleName()); return; diff --git a/tweed5-weaver-pojo-serde-extension/src/test/java/de/siphalor/tweed5/weaver/pojoext/serde/WeaverPojoSerdeExtensionTest.java b/tweed5-weaver-pojo-serde-extension/src/test/java/de/siphalor/tweed5/weaver/pojoext/serde/WeaverPojoSerdeExtensionTest.java index 7b62ce6..4a26fa7 100644 --- a/tweed5-weaver-pojo-serde-extension/src/test/java/de/siphalor/tweed5/weaver/pojoext/serde/WeaverPojoSerdeExtensionTest.java +++ b/tweed5-weaver-pojo-serde-extension/src/test/java/de/siphalor/tweed5/weaver/pojoext/serde/WeaverPojoSerdeExtensionTest.java @@ -32,8 +32,7 @@ class WeaverPojoSerdeExtensionTest { ConfigContainer configContainer = weaverBootstrapper.weave(); configContainer.initialize(); - ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class); - assertThat(readWriteExtension).isNotNull(); + ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); AnnotatedConfig config = new AnnotatedConfig(123, "test", new TestClass(987)); 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 50ef7f3..68a6e2c 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 @@ -3,7 +3,6 @@ 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.core.api.extension.TweedExtension; import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator; import de.siphalor.tweed5.patchwork.impl.PatchworkClass; import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator; @@ -15,7 +14,6 @@ import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext; import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor; import lombok.*; import lombok.extern.slf4j.Slf4j; -import org.jspecify.annotations.Nullable; import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; @@ -44,21 +42,12 @@ public class TweedPojoWeaverBootstrapper { Collection weavers = loadWeavers(Arrays.asList(rootWeavingConfig.weavers())); Collection postProcessors = loadPostProcessors(Arrays.asList(rootWeavingConfig.postProcessors())); - Collection extensions = loadExtensions(Arrays.asList(rootWeavingConfig.extensions())); - configContainer.registerExtensions(extensions.toArray(new TweedExtension[0])); + configContainer.registerExtensions(rootWeavingConfig.extensions()); configContainer.finishExtensionSetup(); return new TweedPojoWeaverBootstrapper<>(pojoClass, configContainer, weavers, postProcessors); } - private static Collection loadExtensions(Collection> extensionClasses) { - try { - return loadSingleServices(extensionClasses); - } catch (Exception e) { - throw new PojoWeavingException("Failed to load Tweed extensions", e); - } - } - private static Collection loadWeavers(Collection> weaverClasses) { return weaverClasses.stream() .map(weaverClass -> TweedPojoWeaver.FACTORY.construct(weaverClass).finish()) @@ -79,60 +68,6 @@ public class TweedPojoWeaverBootstrapper { } } - - private static Collection loadSingleServices(Collection> serviceClasses) { - Collection services = new ArrayList<>(serviceClasses.size()); - for (Class serviceClass : serviceClasses) { - try { - services.add(loadSingleService(serviceClass)); - } catch (Exception e) { - throw new PojoWeavingException("Failed to instantiate single service " + serviceClass.getName(), e); - } - } - return services; - } - - private static S loadSingleService(Class serviceClass) { - try { - ServiceLoader loader = ServiceLoader.load(serviceClass); - Iterator iterator = loader.iterator(); - if (!iterator.hasNext()) { - throw new PojoWeavingException("Could not find any service for class " + serviceClass.getName()); - } - S service = iterator.next(); - - if (iterator.hasNext()) { - throw new PojoWeavingException( - "Found multiple services for class " + serviceClass.getName() + ": " + - createInstanceDebugStringFromIterator(loader.iterator()) - ); - } - - return service; - } catch (ServiceConfigurationError e) { - throw new PojoWeavingException("Failed to load service " + serviceClass.getName(), e); - } - } - - private static String createInstanceDebugStringFromIterator(Iterator iterator) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("[ "); - while (iterator.hasNext()) { - stringBuilder.append(createInstanceDebugDescriptor(iterator.next())); - stringBuilder.append(", "); - } - stringBuilder.append(" ]"); - return stringBuilder.toString(); - } - - private static String createInstanceDebugDescriptor(@Nullable Object object) { - if (object == null) { - return "null"; - } else { - return object.getClass().getName() + "@" + System.identityHashCode(object); - } - } - private static A expectAnnotation(Class clazz, Class annotationClass) { A annotation = clazz.getAnnotation(annotationClass); if (annotation == null) { 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 7a25c55..067fb56 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 @@ -4,6 +4,7 @@ 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; @@ -12,6 +13,7 @@ import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfi import lombok.AllArgsConstructor; 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; @@ -20,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; @SuppressWarnings("unused") +@NullUnmarked class CompoundPojoWeaverTest { @Test @@ -39,10 +42,7 @@ class CompoundPojoWeaverTest { if (entry != null) { return entry; } else { - //noinspection unchecked - ConfigEntry configEntry = mock((Class>) (Class) SimpleConfigEntry.class); - when(configEntry.valueClass()).thenReturn(valueType.declaredType()); - return configEntry; + return new SimpleConfigEntryImpl<>(valueType.declaredType()); } } }, mock(ConfigContainer.class))