[*] Rework registration of TweedExtensions
This commit is contained in:
@@ -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.<br />
|
||||
@@ -20,16 +21,14 @@ public interface ConfigContainer<T> {
|
||||
@SuppressWarnings("rawtypes")
|
||||
TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build();
|
||||
|
||||
default void registerExtensions(TweedExtension... extensions) {
|
||||
for (TweedExtension extension : extensions) {
|
||||
registerExtension(extension);
|
||||
default void registerExtensions(Class<? extends TweedExtension>... extensionClasses) {
|
||||
for (Class<? extends TweedExtension> extensionClass : extensionClasses) {
|
||||
registerExtension(extensionClass);
|
||||
}
|
||||
}
|
||||
void registerExtension(Class<? extends TweedExtension> extensionClass);
|
||||
|
||||
void registerExtension(TweedExtension extension);
|
||||
|
||||
@Nullable
|
||||
<E extends TweedExtension> E extension(Class<E> extensionClass);
|
||||
<E extends TweedExtension> Optional<E> extension(Class<E> extensionClass);
|
||||
Collection<TweedExtension> extensions();
|
||||
|
||||
void finishExtensionSetup();
|
||||
|
||||
@@ -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<TweedExtension> 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) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package de.siphalor.tweed5.core.api.extension;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
|
||||
public interface TweedExtensionSetupContext {
|
||||
ConfigContainer<?> configContainer();
|
||||
<E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass);
|
||||
void registerExtension(TweedExtension extension);
|
||||
void registerExtension(Class<? extends TweedExtension> extensionClass);
|
||||
}
|
||||
|
||||
@@ -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<T> implements ConfigContainer<T> {
|
||||
@Getter
|
||||
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
||||
private final Set<Class<? extends TweedExtension>> requestedExtensions = new HashSet<>();
|
||||
private final InheritanceMap<TweedExtension> extensions = new InheritanceMap<>(TweedExtension.class);
|
||||
private ConfigEntry<T> rootEntry;
|
||||
private PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
||||
private Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions;
|
||||
private @Nullable ConfigEntry<T> rootEntry;
|
||||
private @Nullable PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
||||
private final Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions
|
||||
= new HashMap<>();
|
||||
|
||||
@Override
|
||||
public <E extends TweedExtension> @Nullable E extension(Class<E> extensionClass) {
|
||||
try {
|
||||
return extensions.getSingleInstance(extensionClass);
|
||||
} catch (InheritanceMap.NonUniqueResultException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TweedExtension> extensions() {
|
||||
return Collections.unmodifiableCollection(extensions.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerExtension(TweedExtension extension) {
|
||||
public void registerExtension(Class<? extends TweedExtension> 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<TweedExtension> additionalExtensions = new ArrayList<>();
|
||||
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
|
||||
@Override
|
||||
public ConfigContainer<T> configContainer() {
|
||||
return DefaultConfigContainer.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass) {
|
||||
if (registeredEntryDataExtensions.containsKey(dataClass)) {
|
||||
@@ -73,24 +54,31 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerExtension(TweedExtension extension) {
|
||||
if (!extensions.containsAnyInstanceForClass(extension.getClass())) {
|
||||
additionalExtensions.add(extension);
|
||||
public void registerExtension(Class<? extends TweedExtension> extensionClass) {
|
||||
if (!extensions.containsAnyInstanceForClass(extensionClass)) {
|
||||
requestedExtensions.add(extensionClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Collection<TweedExtension> extensionsToSetup = extensions.values();
|
||||
while (!extensionsToSetup.isEmpty()) {
|
||||
for (TweedExtension extension : extensionsToSetup) {
|
||||
extension.setup(extensionSetupContext);
|
||||
}
|
||||
Set<Class<? extends TweedExtension>> abstractExtensionClasses = new HashSet<>();
|
||||
|
||||
for (TweedExtension additionalExtension : additionalExtensions) {
|
||||
extensions.putIfAbsent(additionalExtension);
|
||||
while (true) {
|
||||
if (!requestedExtensions.isEmpty()) {
|
||||
Class<? extends TweedExtension> extensionClass = popFromIterable(requestedExtensions);
|
||||
if (isAbstractClass(extensionClass)) {
|
||||
abstractExtensionClasses.add(extensionClass);
|
||||
} else {
|
||||
extensions.put(instantiateExtension(extensionClass, extensionSetupContext));
|
||||
}
|
||||
} else if (!abstractExtensionClasses.isEmpty()) {
|
||||
Class<? extends TweedExtension> extensionClass = popFromIterable(abstractExtensionClasses);
|
||||
if (!extensions.containsAnyInstanceForClass(extensionClass)) {
|
||||
extensions.put(instantiateAbstractExtension(extensionClass, extensionSetupContext));
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
extensionsToSetup = new ArrayList<>(additionalExtensions);
|
||||
additionalExtensions.clear();
|
||||
}
|
||||
|
||||
PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()
|
||||
@@ -109,16 +97,135 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
}
|
||||
|
||||
setupPhase = ConfigContainerSetupPhase.TREE_SETUP;
|
||||
|
||||
extensions.values().forEach(TweedExtension::extensionsFinalized);
|
||||
}
|
||||
|
||||
private static <T> T popFromIterable(Iterable<T> iterable) {
|
||||
Iterator<T> iterator = iterable.iterator();
|
||||
T value = iterator.next();
|
||||
iterator.remove();
|
||||
return value;
|
||||
}
|
||||
|
||||
protected <E extends TweedExtension> E instantiateAbstractExtension(
|
||||
Class<E> 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<? extends E>) 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 extends TweedExtension> E instantiateExtension(
|
||||
Class<E> 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 <E extends TweedExtension> Optional<E> extension(Class<E> 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<TweedExtension> extensions() {
|
||||
requireSetupPhase(
|
||||
ConfigContainerSetupPhase.TREE_SETUP,
|
||||
ConfigContainerSetupPhase.TREE_SEALED,
|
||||
ConfigContainerSetupPhase.READY
|
||||
);
|
||||
return Collections.unmodifiableCollection(extensions.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAndSealTree(ConfigEntry<T> 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<T> implements ConfigContainer<T> {
|
||||
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<T> implements ConfigContainer<T> {
|
||||
|
||||
@Override
|
||||
public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() {
|
||||
requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED);
|
||||
requireSetupPhase(
|
||||
ConfigContainerSetupPhase.TREE_SETUP,
|
||||
ConfigContainerSetupPhase.TREE_SEALED,
|
||||
ConfigContainerSetupPhase.READY
|
||||
);
|
||||
return registeredEntryDataExtensions;
|
||||
}
|
||||
|
||||
@@ -149,6 +261,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
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<T> implements ConfigContainer<T> {
|
||||
|
||||
@Override
|
||||
public ConfigEntry<T> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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.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<EntryExtensionsData, ExtensionBData>)
|
||||
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<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.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<? extends ExtensionB> 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<ConfigEntry<?>> 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<ExtensionSelfReferencingDefault> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user