[*] Remove config entry sealing

This commit is contained in:
2025-06-10 00:46:03 +02:00
parent a6900e673a
commit 2096ae540c
28 changed files with 201 additions and 163 deletions

View File

@@ -50,6 +50,7 @@ dependencies {
testImplementation(libs.mockito) testImplementation(libs.mockito)
testAgent(libs.mockito) testAgent(libs.mockito)
testImplementation(libs.assertj) testImplementation(libs.assertj)
testImplementation(project(":test-utils"))
} }
tasks.compileTestJava { tasks.compileTestJava {

View File

@@ -1,5 +1,6 @@
rootProject.name = "tweed5" rootProject.name = "tweed5"
include("test-utils")
include("tweed5-construct") include("tweed5-construct")
include("tweed5-core") include("tweed5-core")
include("tweed5-default-extensions") include("tweed5-default-extensions")

View File

@@ -0,0 +1,3 @@
plugins {
java
}

View File

@@ -0,0 +1,11 @@
package de.siphalor.tweed5.testutils;
import java.util.*;
public class MapTestUtils {
public static <K, V> SequencedMap<K, V> sequencedMap(Collection<Map.Entry<K, V>> entries) {
var map = LinkedHashMap.<K, V>newLinkedHashMap(entries.size());
entries.forEach(entry -> map.put(entry.getKey(), entry.getValue()));
return Collections.unmodifiableSequencedMap(map);
}
}

View File

@@ -5,7 +5,6 @@ import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import org.jspecify.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@@ -33,7 +32,7 @@ public interface ConfigContainer<T> {
void finishExtensionSetup(); void finishExtensionSetup();
void attachAndSealTree(ConfigEntry<T> rootEntry); void attachTree(ConfigEntry<T> rootEntry);
EntryExtensionsData createExtensionsData(); EntryExtensionsData createExtensionsData();

View File

@@ -3,6 +3,6 @@ package de.siphalor.tweed5.core.api.container;
public enum ConfigContainerSetupPhase { public enum ConfigContainerSetupPhase {
EXTENSIONS_SETUP, EXTENSIONS_SETUP,
TREE_SETUP, TREE_SETUP,
TREE_SEALED, TREE_ATTACHED,
READY, READY,
} }

View File

@@ -3,29 +3,16 @@ package de.siphalor.tweed5.core.api.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter @Getter
public abstract class BaseConfigEntry<T> implements ConfigEntry<T> { public abstract class BaseConfigEntry<T> implements ConfigEntry<T> {
private final ConfigContainer<?> container;
private final Class<T> valueClass; private final Class<T> valueClass;
private ConfigContainer<?> container; private final EntryExtensionsData extensionsData;
private EntryExtensionsData extensionsData;
private boolean sealed;
@Override
public void seal(ConfigContainer<?> container) {
requireUnsealed();
public BaseConfigEntry(ConfigContainer<?> container, Class<T> valueClass) {
this.container = container; this.container = container;
this.valueClass = valueClass;
this.extensionsData = container.createExtensionsData(); this.extensionsData = container.createExtensionsData();
sealed = true;
}
protected void requireUnsealed() {
if (sealed) {
throw new IllegalStateException("Config entry is already sealed!");
}
} }
} }

View File

@@ -4,10 +4,10 @@ import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
public interface ConfigEntry<T> { public interface ConfigEntry<T> {
Class<T> valueClass();
void seal(ConfigContainer<?> container); ConfigContainer<?> container();
boolean sealed();
Class<T> valueClass();
EntryExtensionsData extensionsData(); EntryExtensionsData extensionsData();

View File

@@ -200,7 +200,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
public <E extends TweedExtension> Optional<E> extension(Class<E> extensionClass) { public <E extends TweedExtension> Optional<E> extension(Class<E> extensionClass) {
requireSetupPhase( requireSetupPhase(
ConfigContainerSetupPhase.TREE_SETUP, ConfigContainerSetupPhase.TREE_SETUP,
ConfigContainerSetupPhase.TREE_SEALED, ConfigContainerSetupPhase.TREE_ATTACHED,
ConfigContainerSetupPhase.READY ConfigContainerSetupPhase.READY
); );
try { try {
@@ -214,25 +214,19 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
public Collection<TweedExtension> extensions() { public Collection<TweedExtension> extensions() {
requireSetupPhase( requireSetupPhase(
ConfigContainerSetupPhase.TREE_SETUP, ConfigContainerSetupPhase.TREE_SETUP,
ConfigContainerSetupPhase.TREE_SEALED, ConfigContainerSetupPhase.TREE_ATTACHED,
ConfigContainerSetupPhase.READY ConfigContainerSetupPhase.READY
); );
return Collections.unmodifiableCollection(extensions.values()); return Collections.unmodifiableCollection(extensions.values());
} }
@Override @Override
public void attachAndSealTree(ConfigEntry<T> rootEntry) { public void attachTree(ConfigEntry<T> rootEntry) {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP); requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP);
this.rootEntry = rootEntry; this.rootEntry = rootEntry;
rootEntry.visitInOrder(entry -> { setupPhase = ConfigContainerSetupPhase.TREE_ATTACHED;
if (!entry.sealed()) {
entry.seal(DefaultConfigContainer.this);
}
});
setupPhase = ConfigContainerSetupPhase.TREE_SEALED;
} }
@Override @Override
@@ -251,7 +245,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() { public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() {
requireSetupPhase( requireSetupPhase(
ConfigContainerSetupPhase.TREE_SETUP, ConfigContainerSetupPhase.TREE_SETUP,
ConfigContainerSetupPhase.TREE_SEALED, ConfigContainerSetupPhase.TREE_ATTACHED,
ConfigContainerSetupPhase.READY ConfigContainerSetupPhase.READY
); );
return registeredEntryDataExtensions; return registeredEntryDataExtensions;
@@ -259,7 +253,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
@Override @Override
public void initialize() { public void initialize() {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED); requireSetupPhase(ConfigContainerSetupPhase.TREE_ATTACHED);
assert rootEntry != null; assert rootEntry != null;
rootEntry.visitInOrder(entry -> { rootEntry.visitInOrder(entry -> {
@@ -273,7 +267,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
@Override @Override
public ConfigEntry<T> rootEntry() { public ConfigEntry<T> rootEntry() {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED, ConfigContainerSetupPhase.READY); requireSetupPhase(ConfigContainerSetupPhase.TREE_ATTACHED, ConfigContainerSetupPhase.READY);
assert rootEntry != null; assert rootEntry != null;
return rootEntry; return rootEntry;

View File

@@ -1,5 +1,6 @@
package de.siphalor.tweed5.core.impl.entry; package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.*; import de.siphalor.tweed5.core.api.entry.*;
import java.util.Collection; import java.util.Collection;
@@ -7,16 +8,16 @@ import java.util.function.IntFunction;
public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements CollectionConfigEntry<E, T> { public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements CollectionConfigEntry<E, T> {
private final IntFunction<T> collectionConstructor; private final IntFunction<T> collectionConstructor;
private ConfigEntry<E> elementEntry; private final ConfigEntry<E> elementEntry;
public CollectionConfigEntryImpl(Class<T> valueClass, IntFunction<T> collectionConstructor) { public CollectionConfigEntryImpl(
super(valueClass); ConfigContainer<?> container,
Class<T> valueClass,
IntFunction<T> collectionConstructor,
ConfigEntry<E> elementEntry
) {
super(container, valueClass);
this.collectionConstructor = collectionConstructor; this.collectionConstructor = collectionConstructor;
}
public void elementEntry(ConfigEntry<E> elementEntry) {
requireUnsealed();
this.elementEntry = elementEntry; this.elementEntry = elementEntry;
} }

View File

@@ -1,13 +1,14 @@
package de.siphalor.tweed5.core.impl.entry; package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.BaseConfigEntry; import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements SimpleConfigEntry<T> { public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements SimpleConfigEntry<T> {
public SimpleConfigEntryImpl(Class<T> valueClass) { public SimpleConfigEntryImpl(ConfigContainer<?> container, Class<T> valueClass) {
super(valueClass); super(container, valueClass);
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package de.siphalor.tweed5.core.impl.entry; package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.*; import de.siphalor.tweed5.core.api.entry.*;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -8,16 +9,17 @@ import java.util.function.IntFunction;
public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> extends BaseConfigEntry<T> implements CompoundConfigEntry<T> { public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> extends BaseConfigEntry<T> implements CompoundConfigEntry<T> {
private final IntFunction<T> mapConstructor; private final IntFunction<T> mapConstructor;
private final Map<String, ConfigEntry<?>> compoundEntries = new LinkedHashMap<>(); private final Map<String, ConfigEntry<?>> compoundEntries;
public StaticMapCompoundConfigEntryImpl(Class<T> valueClass, IntFunction<T> mapConstructor) { public StaticMapCompoundConfigEntryImpl(
super(valueClass); ConfigContainer<?> container,
Class<T> valueClass,
IntFunction<T> mapConstructor,
Map<String, ConfigEntry<?>> compoundEntries
) {
super(container, valueClass);
this.mapConstructor = mapConstructor; this.mapConstructor = mapConstructor;
} this.compoundEntries = new LinkedHashMap<>(compoundEntries);
public void addSubEntry(String key, ConfigEntry<?> entry) {
requireUnsealed();
compoundEntries.put(key, entry);
} }
@Override @Override

View File

@@ -105,23 +105,23 @@ class DefaultConfigContainerTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
void attachAndSealTree() { void attachTree() {
var configContainer = new DefaultConfigContainer<Map<String, Object>>(); var configContainer = new DefaultConfigContainer<Map<String, Object>>();
var compoundEntry = new StaticMapCompoundConfigEntryImpl<>( configContainer.registerExtension(ExtensionInitTracker.class);
(Class<Map<String, Object>>)(Class<?>) Map.class,
(capacity) -> new HashMap<>(capacity * 2, 0.5F)
);
var subEntry = new SimpleConfigEntryImpl<>(String.class);
compoundEntry.addSubEntry("test", subEntry);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SETUP); var subEntry = new SimpleConfigEntryImpl<>(configContainer, String.class);
configContainer.attachAndSealTree(compoundEntry); var compoundEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
(Class<Map<String, Object>>)(Class<?>) Map.class,
(capacity) -> new HashMap<>(capacity * 2, 0.5F),
Map.of("test", subEntry)
);
assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SEALED); assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SETUP);
assertThat(compoundEntry.sealed()).isTrue(); configContainer.attachTree(compoundEntry);
assertThat(subEntry.sealed()).isTrue();
assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_ATTACHED);
assertThat(configContainer.rootEntry()).isSameAs(compoundEntry); assertThat(configContainer.rootEntry()).isSameAs(compoundEntry);
} }
@@ -155,21 +155,23 @@ class DefaultConfigContainerTest {
assertThat(((ExtensionBData) extensionsData).test()).isEqualTo("blub"); assertThat(((ExtensionBData) extensionsData).test()).isEqualTo("blub");
} }
@SuppressWarnings("unchecked")
@Test @Test
void initialize() { void initialize() {
var configContainer = new DefaultConfigContainer<Map<String, Object>>(); var configContainer = new DefaultConfigContainer<Map<String, Object>>();
var compoundEntry = new StaticMapCompoundConfigEntryImpl<>(
(Class<Map<String, Object>>)(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.registerExtension(ExtensionInitTracker.class);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
configContainer.attachAndSealTree(compoundEntry);
assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_SEALED); var subEntry = new SimpleConfigEntryImpl<>(configContainer, String.class);
var compoundEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
(Class<Map<String, Object>>)(Class<?>) Map.class,
(capacity) -> new HashMap<>(capacity * 2, 0.5F),
Map.of("test", subEntry)
);
configContainer.attachTree(compoundEntry);
assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.TREE_ATTACHED);
configContainer.initialize(); configContainer.initialize();
assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.READY); assertThat(configContainer.setupPhase()).isEqualTo(ConfigContainerSetupPhase.READY);

View File

@@ -28,8 +28,11 @@ import org.junit.jupiter.api.Test;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@NullUnmarked @NullUnmarked
@@ -49,17 +52,23 @@ class CommentExtensionImplTest {
configContainer.registerExtensions(extraExtensions); configContainer.registerExtensions(extraExtensions);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class);
noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class);
//noinspection unchecked //noinspection unchecked
rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>)(Class<?>) Map.class), LinkedHashMap::new); rootEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
((Class<Map<String, Object>>)(Class<?>) Map.class),
LinkedHashMap::new,
sequencedMap(List.of(
entry("int", intEntry),
entry("string", stringEntry),
entry("noComment", noCommentEntry)
))
);
intEntry = new SimpleConfigEntryImpl<>(Integer.class); configContainer.attachTree(rootEntry);
rootEntry.addSubEntry("int", intEntry);
stringEntry = new SimpleConfigEntryImpl<>(String.class);
rootEntry.addSubEntry("string", stringEntry);
noCommentEntry = new SimpleConfigEntryImpl<>(Long.class);
rootEntry.addSubEntry("noComment", noCommentEntry);
configContainer.attachAndSealTree(rootEntry);
//noinspection unchecked //noinspection unchecked
RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class); RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class);

View File

@@ -20,11 +20,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.CsvSource;
import java.util.Collections; import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class ValidationExtensionImplTest { class ValidationExtensionImplTest {
@@ -42,17 +41,24 @@ class ValidationExtensionImplTest {
configContainer.registerExtension(ValidationExtension.DEFAULT); configContainer.registerExtension(ValidationExtension.DEFAULT);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class);
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class);
//noinspection unchecked //noinspection unchecked
rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>) (Class<?>) Map.class), LinkedHashMap::new); rootEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
((Class<Map<String, Object>>) (Class<?>) Map.class),
LinkedHashMap::new,
sequencedMap(List.of(
entry("byte", byteEntry),
entry("int", intEntry),
entry("double", doubleEntry)
))
);
byteEntry = new SimpleConfigEntryImpl<>(Byte.class);
rootEntry.addSubEntry("byte", byteEntry);
intEntry = new SimpleConfigEntryImpl<>(Integer.class);
rootEntry.addSubEntry("int", intEntry);
doubleEntry = new SimpleConfigEntryImpl<>(Double.class);
rootEntry.addSubEntry("double", doubleEntry);
configContainer.attachAndSealTree(rootEntry); configContainer.attachTree(rootEntry);
//noinspection unchecked //noinspection unchecked
RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class); RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class);

View File

@@ -55,9 +55,8 @@ class ValidationFallbackExtensionImplTest {
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
intEntry = new SimpleConfigEntryImpl<>(Integer.class); intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
configContainer.attachTree(intEntry);
configContainer.attachAndSealTree(intEntry);
RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class); RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class);
entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList( entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList(

View File

@@ -8,9 +8,9 @@ import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition; 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.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter; import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters; import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.data.hjson.HjsonLexer; import de.siphalor.tweed5.data.hjson.HjsonLexer;
@@ -27,6 +27,8 @@ import java.io.Writer;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -42,18 +44,26 @@ class ReadWriteExtensionImplTest {
configContainer.registerExtension(ReadWriteExtension.DEFAULT); configContainer.registerExtension(ReadWriteExtension.DEFAULT);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>) (Class<?>) Map.class), LinkedHashMap::new); SimpleConfigEntryImpl<Integer> intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
SimpleConfigEntryImpl<Boolean> booleanEntry = new SimpleConfigEntryImpl<>(configContainer, Boolean.class);
CollectionConfigEntryImpl<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>(
configContainer,
(Class<List<Boolean>>) (Class<?>) List.class,
ArrayList::new,
booleanEntry
);
SimpleConfigEntryImpl<Integer> intEntry = new SimpleConfigEntryImpl<>(Integer.class); rootEntry = new StaticMapCompoundConfigEntryImpl<>(
rootEntry.addSubEntry("int", intEntry); configContainer,
((Class<Map<String, Object>>) (Class<?>) Map.class),
LinkedHashMap::new,
sequencedMap(List.of(
entry("int", intEntry),
entry("list", listEntry)
))
);
CollectionConfigEntryImpl<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>((Class<List<Boolean>>) (Class<?>) List.class, ArrayList::new); configContainer.attachTree(rootEntry);
rootEntry.addSubEntry("list", listEntry);
SimpleConfigEntryImpl<Boolean> booleanEntry = new SimpleConfigEntryImpl<>(Boolean.class);
listEntry.elementEntry(booleanEntry);
configContainer.attachAndSealTree(rootEntry);
RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class); RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter())); readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter()));

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.weaver.pojo.api.entry; package de.siphalor.tweed5.weaver.pojo.api.entry;
import de.siphalor.tweed5.construct.api.TweedConstructFactory; import de.siphalor.tweed5.construct.api.TweedConstructFactory;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry; import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
@@ -17,9 +18,9 @@ public interface WeavableCollectionConfigEntry<E, T extends Collection<E>> exten
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
TweedConstructFactory<WeavableCollectionConfigEntry> FACTORY = TweedConstructFactory<WeavableCollectionConfigEntry> FACTORY =
TweedConstructFactory.builder(WeavableCollectionConfigEntry.class) TweedConstructFactory.builder(WeavableCollectionConfigEntry.class)
.typedArg(ConfigContainer.class)
.typedArg(Class.class) // value class .typedArg(Class.class) // value class
.typedArg(IntFunction.class) // value class constructor with capacity .typedArg(IntFunction.class) // value class constructor with capacity
.namedArg("elementEntry", ConfigEntry.class)
.build(); .build();
void elementEntry(ConfigEntry<E> elementEntry);
} }

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.weaver.pojo.api.entry; package de.siphalor.tweed5.weaver.pojo.api.entry;
import de.siphalor.tweed5.construct.api.TweedConstructFactory; import de.siphalor.tweed5.construct.api.TweedConstructFactory;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -8,6 +9,8 @@ import lombok.Value;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@@ -19,12 +22,12 @@ public interface WeavableCompoundConfigEntry<T> extends CompoundConfigEntry<T> {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
TweedConstructFactory<WeavableCompoundConfigEntry> FACTORY = TweedConstructFactory<WeavableCompoundConfigEntry> FACTORY =
TweedConstructFactory.builder(WeavableCompoundConfigEntry.class) TweedConstructFactory.builder(WeavableCompoundConfigEntry.class)
.typedArg(ConfigContainer.class)
.typedArg(Class.class) // the value class .typedArg(Class.class) // the value class
.typedArg(Supplier.class) // constructor for the value class .typedArg(Supplier.class) // constructor for the value class
.namedArg("subEntries", List.class) // List of SubEntry's
.build(); .build();
void registerSubEntry(SubEntry subEntry);
@Value @Value
@RequiredArgsConstructor @RequiredArgsConstructor
class SubEntry { class SubEntry {

View File

@@ -1,5 +1,6 @@
package de.siphalor.tweed5.weaver.pojo.api.weaving; 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.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.typeutils.api.type.ActualType; import de.siphalor.tweed5.typeutils.api.type.ActualType;
@@ -44,22 +45,21 @@ public class CollectionPojoWeaver implements TweedPojoWeaver {
IntFunction<Collection<Object>> constructor = getCollectionConstructor(valueType); IntFunction<Collection<Object>> constructor = getCollectionConstructor(valueType);
WeavableCollectionConfigEntry configEntry = WeavableCollectionConfigEntry.FACTORY ConfigEntry<?> elementEntry = context.weaveEntry(
.construct(Objects.requireNonNull(weavingConfig.collectionEntryClass()))
.typedArg(valueType.declaredType())
.typedArg(IntFunction.class, constructor)
.finish();
configEntry.elementEntry(context.weaveEntry(
collectionTypeParams.get(0), collectionTypeParams.get(0),
context.subContextBuilder("element") context.subContextBuilder("element")
.annotations(collectionTypeParams.get(0)) .annotations(collectionTypeParams.get(0))
.extensionsData(newExtensionsData) .extensionsData(newExtensionsData)
.build() .build()
)); );
configEntry.seal(context.configContainer());
return configEntry; return WeavableCollectionConfigEntry.FACTORY
.construct(Objects.requireNonNull(weavingConfig.collectionEntryClass()))
.typedArg(ConfigContainer.class, context.configContainer())
.typedArg(valueType.declaredType())
.typedArg(IntFunction.class, constructor)
.namedArg("elementEntry", elementEntry)
.finish();
} catch (Exception e) { } catch (Exception e) {
throw new PojoWeavingException("Exception occurred trying to weave collection for class " + valueType, e); throw new PojoWeavingException("Exception occurred trying to weave collection for class " + valueType, e);
} }

View File

@@ -1,5 +1,6 @@
package de.siphalor.tweed5.weaver.pojo.api.weaving; 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.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.namingformat.api.NamingFormat; import de.siphalor.tweed5.namingformat.api.NamingFormat;
@@ -20,8 +21,10 @@ import org.jspecify.annotations.Nullable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
/** /**
* A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries. * A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries.
@@ -54,18 +57,12 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType()); PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType());
WeavableCompoundConfigEntry<T> compoundEntry = instantiateCompoundEntry(introspector, weavingConfig); List<WeavableCompoundConfigEntry.SubEntry> subEntries = introspector.properties().values().stream()
.filter(this::shouldIncludeCompoundPropertyInWeaving)
.map(property -> weaveCompoundSubEntry(property, newExtensionsData, context))
.collect(Collectors.toList());
Map<String, PojoClassIntrospector.Property> properties = introspector.properties(); return instantiateCompoundEntry(introspector, weavingConfig, subEntries, context);
properties.forEach((name, property) -> {
if (shouldIncludeCompoundPropertyInWeaving(property)) {
compoundEntry.registerSubEntry(weaveCompoundSubEntry(property, newExtensionsData, context));
}
});
compoundEntry.seal(context.configContainer());
return compoundEntry;
} catch (Exception e) { } catch (Exception e) {
throw new PojoWeavingException("Exception occurred trying to weave compound for class " + valueType, e); throw new PojoWeavingException("Exception occurred trying to weave compound for class " + valueType, e);
} }
@@ -109,7 +106,9 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <C> WeavableCompoundConfigEntry<C> instantiateCompoundEntry( private <C> WeavableCompoundConfigEntry<C> instantiateCompoundEntry(
PojoClassIntrospector classIntrospector, PojoClassIntrospector classIntrospector,
CompoundWeavingConfig weavingConfig CompoundWeavingConfig weavingConfig,
List<WeavableCompoundConfigEntry.SubEntry> subEntries,
WeavingContext weavingContext
) { ) {
MethodHandle valueConstructorHandle = classIntrospector.noArgsConstructor(); MethodHandle valueConstructorHandle = classIntrospector.noArgsConstructor();
if (valueConstructorHandle == null) { if (valueConstructorHandle == null) {
@@ -131,8 +130,10 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
: StaticPojoCompoundConfigEntry.class : StaticPojoCompoundConfigEntry.class
); );
return WeavableCompoundConfigEntry.FACTORY.construct(weavableEntryClass) return WeavableCompoundConfigEntry.FACTORY.construct(weavableEntryClass)
.typedArg(ConfigContainer.class, weavingContext.configContainer())
.typedArg(classIntrospector.type()) .typedArg(classIntrospector.type())
.typedArg(Supplier.class, valueConstructor) .typedArg(Supplier.class, valueConstructor)
.namedArg("subEntries", subEntries)
.finish(); .finish();
} }

View File

@@ -12,8 +12,6 @@ public class TrivialPojoWeaver implements TweedPojoWeaver {
@Override @Override
public <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) { public <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
SimpleConfigEntryImpl<T> entry = new SimpleConfigEntryImpl<>(valueType.declaredType()); return new SimpleConfigEntryImpl<>(context.configContainer(), valueType.declaredType());
entry.seal(context.configContainer());
return entry;
} }
} }

View File

@@ -8,8 +8,7 @@ import org.jspecify.annotations.Nullable;
public interface TweedPojoWeavingFunction { public interface TweedPojoWeavingFunction {
/** /**
* Weaves a {@link ConfigEntry} for the given value class and context. * Weaves a {@link ConfigEntry} for the given value class and context.
* The returned config entry must be sealed. * @return The resulting config entry or {@code null}, if the weaving function is not applicable to the given parameters.
* @return The resulting, sealed config entry or {@code null}, if the weaving function is not applicable to the given parameters.
*/ */
<T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context); <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context);
@@ -19,7 +18,7 @@ public interface TweedPojoWeavingFunction {
* {@inheritDoc} * {@inheritDoc}
* <br /> * <br />
* The function must ensure that the resulting entry is not null, e.g., by trowing a {@link RuntimeException}. * The function must ensure that the resulting entry is not null, e.g., by trowing a {@link RuntimeException}.
* @return The resulting, sealed config entry. * @return The resulting config entry.
* @throws RuntimeException when a valid config entry could not be resolved. * @throws RuntimeException when a valid config entry could not be resolved.
*/ */
@Override @Override

View File

@@ -1,5 +1,7 @@
package de.siphalor.tweed5.weaver.pojo.impl.entry; package de.siphalor.tweed5.weaver.pojo.impl.entry;
import de.siphalor.tweed5.construct.api.ConstructParameter;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.BaseConfigEntry; import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
@@ -19,15 +21,16 @@ import java.util.function.IntFunction;
@ToString(callSuper = true) @ToString(callSuper = true)
public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements WeavableCollectionConfigEntry<E, T> { public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements WeavableCollectionConfigEntry<E, T> {
private final IntFunction<T> constructor; private final IntFunction<T> constructor;
private @Nullable ConfigEntry<E> elementEntry; private final @Nullable ConfigEntry<E> elementEntry;
public CollectionConfigEntryImpl(@NotNull Class<T> valueClass, IntFunction<T> constructor) { public CollectionConfigEntryImpl(
super(valueClass); ConfigContainer<?> configContainer,
Class<T> valueClass,
IntFunction<T> constructor,
@ConstructParameter(name = "elementEntry") ConfigEntry<E> elementEntry
) {
super(configContainer, valueClass);
this.constructor = constructor; this.constructor = constructor;
}
@Override
public void elementEntry(ConfigEntry<E> elementEntry) {
this.elementEntry = elementEntry; this.elementEntry = elementEntry;
} }

View File

@@ -1,5 +1,7 @@
package de.siphalor.tweed5.weaver.pojo.impl.entry; package de.siphalor.tweed5.weaver.pojo.impl.entry;
import de.siphalor.tweed5.construct.api.ConstructParameter;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.BaseConfigEntry; import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
@@ -8,22 +10,32 @@ import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> implements WeavableCompoundConfigEntry<T> { public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> implements WeavableCompoundConfigEntry<T> {
private final Supplier<T> noArgsConstructor; private final Supplier<T> noArgsConstructor;
private final Map<String, SubEntry> subEntries = new LinkedHashMap<>(); private final Map<String, SubEntry> subEntries;
private final Map<String, ConfigEntry<?>> subConfigEntries = new LinkedHashMap<>(); private final Map<String, ConfigEntry<?>> subConfigEntries;
public StaticPojoCompoundConfigEntry(Class<T> valueClass, Supplier<T> noArgsConstructor) { public StaticPojoCompoundConfigEntry(
super(valueClass); ConfigContainer<?> configContainer,
Class<T> valueClass,
Supplier<T> noArgsConstructor,
@ConstructParameter(name = "subEntries") List<SubEntry> subEntries
) {
super(configContainer, valueClass);
this.noArgsConstructor = noArgsConstructor; this.noArgsConstructor = noArgsConstructor;
this.subEntries = new LinkedHashMap<>(subEntries.size(), 1);
this.subConfigEntries = new LinkedHashMap<>(subEntries.size(), 1);
for (SubEntry subEntry : subEntries) {
this.subEntries.put(subEntry.name(), subEntry);
this.subConfigEntries.put(subEntry.name(), subEntry.configEntry());
}
} }
public void registerSubEntry(SubEntry subEntry) { public void registerSubEntry(SubEntry subEntry) {
requireUnsealed();
subEntries.put(subEntry.name(), subEntry); subEntries.put(subEntry.name(), subEntry);
subConfigEntries.put(subEntry.name(), subEntry.configEntry()); subConfigEntries.put(subEntry.name(), subEntry.configEntry());
} }

View File

@@ -82,7 +82,7 @@ public class TweedPojoWeaverBootstrapper<T> {
WeavingContext weavingContext = createWeavingContext(); WeavingContext weavingContext = createWeavingContext();
ConfigEntry<T> rootEntry = this.weaveEntry(ActualType.ofClass(pojoClass), weavingContext); ConfigEntry<T> rootEntry = this.weaveEntry(ActualType.ofClass(pojoClass), weavingContext);
configContainer.attachAndSealTree(rootEntry); configContainer.attachTree(rootEntry);
return configContainer; return configContainer;
} }
@@ -140,9 +140,6 @@ public class TweedPojoWeaverBootstrapper<T> {
for (TweedPojoWeaver weaver : weavers) { for (TweedPojoWeaver weaver : weavers) {
ConfigEntry<U> configEntry = weaver.weaveEntry(dataClass, context); ConfigEntry<U> configEntry = weaver.weaveEntry(dataClass, context);
if (configEntry != null) { if (configEntry != null) {
if (!configEntry.sealed()) {
configEntry.seal(configContainer);
}
applyPostProcessors(configEntry, context); applyPostProcessors(configEntry, context);
return configEntry; return configEntry;
} }

View File

@@ -42,7 +42,7 @@ class CompoundPojoWeaverTest {
if (entry != null) { if (entry != null) {
return entry; return entry;
} else { } else {
return new SimpleConfigEntryImpl<>(valueType.declaredType()); return new SimpleConfigEntryImpl<>(context.configContainer(), valueType.declaredType());
} }
} }
}, mock(ConfigContainer.class)) }, mock(ConfigContainer.class))

View File

@@ -36,8 +36,6 @@ class TweedPojoWeaverBootstrapperTest {
.hasSize(5) .hasSize(5)
)); ));
configContainer.initialize();
assertThat(configContainer.extensions()) assertThat(configContainer.extensions())
.satisfiesOnlyOnce(extension -> assertThat(extension).isInstanceOf(DummyExtension.class)) .satisfiesOnlyOnce(extension -> assertThat(extension).isInstanceOf(DummyExtension.class))
.hasSize(1); .hasSize(1);