[*] Rework registration of TweedExtensions
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<? extends CommentExtension> DEFAULT = CommentExtensionImpl.class;
|
||||
|
||||
@Nullable String getFullComment(ConfigEntry<?> configEntry);
|
||||
}
|
||||
|
||||
@@ -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<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension;
|
||||
private DefaultMiddlewareContainer<CommentProducer> middlewareContainer;
|
||||
private final RegisteredExtensionData<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension;
|
||||
private final DefaultMiddlewareContainer<CommentProducer> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<? extends PatherExtension> DEFAULT = PatherExtensionImpl.class;
|
||||
}
|
||||
|
||||
@@ -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<ReadWriteContextExtensionsData, PathTracking> rwContextPathTrackingData;
|
||||
private RegisteredExtensionData<ReadWriteContextExtensionsData, PatherData> rwContextPathTrackingData;
|
||||
private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware;
|
||||
private Middleware<TweedEntryWriter<?, ?>> 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<Object, @NonNull ConfigEntry<Object>>) inner;
|
||||
|
||||
return (TweedDataReader reader, ConfigEntry<Object> 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<Object, @NonNull ConfigEntry<Object>>) inner;
|
||||
|
||||
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) -> {
|
||||
if (context.extensionsData().isPatchworkPartSet(PathTracking.class)) {
|
||||
if (context.extensionsData().isPatchworkPartSet(PatherData.class)) {
|
||||
castedInner.write(writer, value, entry, context);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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<? extends ValidationExtension> DEFAULT = ValidationExtensionImpl.class;
|
||||
|
||||
<T extends @Nullable Object> ValidationIssues validate(ConfigEntry<T> entry, T value);
|
||||
}
|
||||
|
||||
@@ -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<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
|
||||
private MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer;
|
||||
private RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues> readContextValidationIssuesExtensionData;
|
||||
private final ConfigContainer<?> configContainer;
|
||||
private final RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
|
||||
private final MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer
|
||||
= new DefaultMiddlewareContainer<>();
|
||||
private @Nullable RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues>
|
||||
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();
|
||||
|
||||
@@ -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<? extends ValidationFallbackExtension> DEFAULT = ValidationFallbackExtensionImpl.class;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Map<String, Object>> configContainer;
|
||||
private CommentExtension commentExtension;
|
||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
||||
private SimpleConfigEntryImpl<String> stringEntry;
|
||||
private SimpleConfigEntryImpl<Long> noCommentEntry;
|
||||
|
||||
void setupContainer(Collection<TweedExtension> extraExtensions) {
|
||||
@SafeVarargs
|
||||
final void setupContainer(Class<? extends TweedExtension>... 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<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
|
||||
var readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) 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";
|
||||
|
||||
@@ -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<Map<String, Object>> configContainer;
|
||||
private CommentExtension commentExtension;
|
||||
private ValidationExtension validationExtension;
|
||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntryImpl<Byte> byteEntry;
|
||||
private SimpleConfigEntryImpl<Integer> 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());
|
||||
|
||||
@@ -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<Integer> configContainer;
|
||||
private CommentExtension commentExtension;
|
||||
private ValidationExtension validationExtension;
|
||||
private ValidationFallbackExtension validationFallbackExtension;
|
||||
private ReadWriteExtension readWriteExtension;
|
||||
private SimpleConfigEntryImpl<Integer> 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 <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {
|
||||
public <T> ValidationResult<T> validate(ConfigEntry<T> 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 <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {
|
||||
public <T> ValidationResult<T> validate(ConfigEntry<T> 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());
|
||||
}
|
||||
}
|
||||
@@ -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<Type> 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 {
|
||||
|
||||
@@ -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<? extends ReadWriteExtension> DEFAULT = ReadWriteExtensionImpl.class;
|
||||
|
||||
void setEntryReaderWriterDefinition(ConfigEntry<?> entry, EntryReaderWriterDefinition readerWriterDefinition);
|
||||
|
||||
ReadWriteContextExtensionsData createReadWriteContextExtensionsData();
|
||||
|
||||
@@ -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<EntryExtensionsData, EntryReaderWriterDefinition>
|
||||
readerWriterDefinitionExtension;
|
||||
private final RegisteredExtensionData<EntryExtensionsData, ReadWriteEntryDataExtension> readWriteEntryDataExtension;
|
||||
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>>
|
||||
entryReaderMiddlewareContainer
|
||||
= new DefaultMiddlewareContainer<>();
|
||||
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>>
|
||||
entryWriterMiddlewareContainer
|
||||
= new DefaultMiddlewareContainer<>();
|
||||
private final Map<Class<?>, RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>>
|
||||
readWriteContextExtensionsDataClasses
|
||||
= new HashMap<>();
|
||||
private @Nullable PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork;
|
||||
|
||||
private RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterDefinitionExtension;
|
||||
private RegisteredExtensionData<EntryExtensionsData, ReadWriteEntryDataExtension> readWriteEntryDataExtension;
|
||||
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>> entryReaderMiddlewareContainer;
|
||||
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>> entryWriterMiddlewareContainer;
|
||||
private Map<Class<?>, RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>>
|
||||
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<TweedExtension> extensions = context.configContainer().extensions();
|
||||
|
||||
readWriteContextExtensionsDataClasses = new HashMap<>(extensions.size());
|
||||
public void extensionsFinalized() {
|
||||
Collection<TweedExtension> 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> T read(
|
||||
public <T extends @Nullable Object> T read(
|
||||
@NonNull TweedDataReader reader,
|
||||
@NonNull ConfigEntry<T> entry,
|
||||
@NonNull ReadWriteContextExtensionsData contextExtensionsData
|
||||
@@ -174,7 +181,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void write(
|
||||
public <T extends @Nullable Object> void write(
|
||||
@NonNull TweedDataVisitor writer,
|
||||
@Nullable T value,
|
||||
@NonNull ConfigEntry<T> entry,
|
||||
|
||||
@@ -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<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
|
||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||
private ReadWriteExtension readWriteExtension;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
|
||||
|
||||
readWriteExtension = new ReadWriteExtensionImpl();
|
||||
configContainer.registerExtension(readWriteExtension);
|
||||
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>) (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<String, Object> 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<Writer, TweedDataVisitor> writerFactory) {
|
||||
stringWriter = new StringWriter();
|
||||
return writerFactory.apply(stringWriter);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -32,8 +32,7 @@ class WeaverPojoSerdeExtensionTest {
|
||||
ConfigContainer<AnnotatedConfig> 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));
|
||||
|
||||
|
||||
@@ -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<T> {
|
||||
Collection<TweedPojoWeaver> weavers = loadWeavers(Arrays.asList(rootWeavingConfig.weavers()));
|
||||
Collection<TweedPojoWeavingPostProcessor> postProcessors = loadPostProcessors(Arrays.asList(rootWeavingConfig.postProcessors()));
|
||||
|
||||
Collection<TweedExtension> 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<TweedExtension> loadExtensions(Collection<Class<? extends TweedExtension>> extensionClasses) {
|
||||
try {
|
||||
return loadSingleServices(extensionClasses);
|
||||
} catch (Exception e) {
|
||||
throw new PojoWeavingException("Failed to load Tweed extensions", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<TweedPojoWeaver> loadWeavers(Collection<Class<? extends TweedPojoWeaver>> weaverClasses) {
|
||||
return weaverClasses.stream()
|
||||
.map(weaverClass -> TweedPojoWeaver.FACTORY.construct(weaverClass).finish())
|
||||
@@ -79,60 +68,6 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static <S> Collection<S> loadSingleServices(Collection<Class<? extends S>> serviceClasses) {
|
||||
Collection<S> services = new ArrayList<>(serviceClasses.size());
|
||||
for (Class<? extends S> 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> S loadSingleService(Class<S> serviceClass) {
|
||||
try {
|
||||
ServiceLoader<S> loader = ServiceLoader.load(serviceClass);
|
||||
Iterator<S> 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 extends Annotation> A expectAnnotation(Class<?> clazz, Class<A> annotationClass) {
|
||||
A annotation = clazz.getAnnotation(annotationClass);
|
||||
if (annotation == null) {
|
||||
|
||||
@@ -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<T> configEntry = mock((Class<SimpleConfigEntry<T>>) (Class<?>) SimpleConfigEntry.class);
|
||||
when(configEntry.valueClass()).thenReturn(valueType.declaredType());
|
||||
return configEntry;
|
||||
return new SimpleConfigEntryImpl<>(valueType.declaredType());
|
||||
}
|
||||
}
|
||||
}, mock(ConfigContainer.class))
|
||||
|
||||
Reference in New Issue
Block a user