[*] Rework registration of TweedExtensions

This commit is contained in:
2025-04-25 15:33:55 +02:00
parent c97f711c0b
commit 59f882bd12
27 changed files with 639 additions and 257 deletions

View File

@@ -11,8 +11,8 @@ group = rootProject.group
version = rootProject.version version = rootProject.version
java { java {
sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) sourceCompatibility = JavaVersion.toVersion(libs.versions.java.main.get())
targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) targetCompatibility = JavaVersion.toVersion(libs.versions.java.main.get())
} }
repositories { repositories {
@@ -52,6 +52,10 @@ dependencies {
testImplementation(libs.assertj) testImplementation(libs.assertj)
} }
tasks.compileTestJava {
sourceCompatibility = libs.versions.java.test.get()
targetCompatibility = libs.versions.java.test.get()
}
tasks.test { tasks.test {
dependsOn(testAgentClasspath) dependsOn(testAgentClasspath)

View File

@@ -2,7 +2,8 @@
assertj = "3.26.3" assertj = "3.26.3"
asm = "9.7" asm = "9.7"
autoservice = "1.1.1" autoservice = "1.1.1"
java = "8" java-main = "8"
java-test = "21"
jetbrains-annotations = "26.0.1" jetbrains-annotations = "26.0.1"
jspecify = "1.0.0" jspecify = "1.0.0"
junit = "5.12.0" junit = "5.12.0"

View File

@@ -9,6 +9,7 @@ import org.jspecify.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Optional;
/** /**
* The main wrapper for a config tree.<br /> * The main wrapper for a config tree.<br />
@@ -20,16 +21,14 @@ public interface ConfigContainer<T> {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build(); TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build();
default void registerExtensions(TweedExtension... extensions) { default void registerExtensions(Class<? extends TweedExtension>... extensionClasses) {
for (TweedExtension extension : extensions) { for (Class<? extends TweedExtension> extensionClass : extensionClasses) {
registerExtension(extension); registerExtension(extensionClass);
} }
} }
void registerExtension(Class<? extends TweedExtension> extensionClass);
void registerExtension(TweedExtension extension); <E extends TweedExtension> Optional<E> extension(Class<E> extensionClass);
@Nullable
<E extends TweedExtension> E extension(Class<E> extensionClass);
Collection<TweedExtension> extensions(); Collection<TweedExtension> extensions();
void finishExtensionSetup(); void finishExtensionSetup();

View File

@@ -1,11 +1,18 @@
package de.siphalor.tweed5.core.api.extension; 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; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
public interface TweedExtension { public interface TweedExtension {
TweedConstructFactory<TweedExtension> FACTORY = TweedConstructFactory.builder(TweedExtension.class)
.typedArg(ConfigContainer.class)
.typedArg(TweedExtensionSetupContext.class)
.build();
String getId(); String getId();
default void setup(TweedExtensionSetupContext context) { default void extensionsFinalized() {
} }
default void initEntry(ConfigEntry<?> configEntry) { default void initEntry(ConfigEntry<?> configEntry) {

View File

@@ -1,9 +1,6 @@
package de.siphalor.tweed5.core.api.extension; package de.siphalor.tweed5.core.api.extension;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
public interface TweedExtensionSetupContext { public interface TweedExtensionSetupContext {
ConfigContainer<?> configContainer();
<E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass); <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass);
void registerExtension(TweedExtension extension); void registerExtension(Class<? extends TweedExtension> extensionClass);
} }

View File

@@ -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.ConfigContainer;
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase; import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; 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.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator; import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
import de.siphalor.tweed5.patchwork.impl.PatchworkClass; 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 de.siphalor.tweed5.utils.api.collection.InheritanceMap;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
@NullUnmarked
public class DefaultConfigContainer<T> implements ConfigContainer<T> { public class DefaultConfigContainer<T> implements ConfigContainer<T> {
@Getter @Getter
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP; 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 final InheritanceMap<TweedExtension> extensions = new InheritanceMap<>(TweedExtension.class);
private ConfigEntry<T> rootEntry; private @Nullable ConfigEntry<T> rootEntry;
private PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass; private @Nullable PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
private Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions; private final Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions
= new HashMap<>();
@Override @Override
public <E extends TweedExtension> @Nullable E extension(Class<E> extensionClass) { public void registerExtension(Class<? extends TweedExtension> 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) {
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP); requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
requestedExtensions.add(extensionClass);
if (!extensions.putIfAbsent(extension)) {
throw new IllegalArgumentException("Extension " + extension.getClass().getName() + " is already registered");
}
} }
@Override @Override
public void finishExtensionSetup() { public void finishExtensionSetup() {
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP); requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
registeredEntryDataExtensions = new HashMap<>();
Collection<TweedExtension> additionalExtensions = new ArrayList<>();
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() { TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
@Override
public ConfigContainer<T> configContainer() {
return DefaultConfigContainer.this;
}
@Override @Override
public <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass) { public <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass) {
if (registeredEntryDataExtensions.containsKey(dataClass)) { if (registeredEntryDataExtensions.containsKey(dataClass)) {
@@ -73,24 +54,31 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
} }
@Override @Override
public void registerExtension(TweedExtension extension) { public void registerExtension(Class<? extends TweedExtension> extensionClass) {
if (!extensions.containsAnyInstanceForClass(extension.getClass())) { if (!extensions.containsAnyInstanceForClass(extensionClass)) {
additionalExtensions.add(extension); requestedExtensions.add(extensionClass);
} }
} }
}; };
Collection<TweedExtension> extensionsToSetup = extensions.values(); Set<Class<? extends TweedExtension>> abstractExtensionClasses = new HashSet<>();
while (!extensionsToSetup.isEmpty()) {
for (TweedExtension extension : extensionsToSetup) {
extension.setup(extensionSetupContext);
}
for (TweedExtension additionalExtension : additionalExtensions) { while (true) {
extensions.putIfAbsent(additionalExtension); 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() PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()
@@ -109,16 +97,135 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
} }
setupPhase = ConfigContainerSetupPhase.TREE_SETUP; 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 @Override
public void attachAndSealTree(ConfigEntry<T> rootEntry) { public void attachAndSealTree(ConfigEntry<T> rootEntry) {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP); requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP);
this.rootEntry = rootEntry;
finishEntrySetup();
}
private void finishEntrySetup() { this.rootEntry = rootEntry;
rootEntry.visitInOrder(entry -> { rootEntry.visitInOrder(entry -> {
if (!entry.sealed()) { if (!entry.sealed()) {
entry.seal(DefaultConfigContainer.this); entry.seal(DefaultConfigContainer.this);
@@ -133,6 +240,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP); requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP);
try { try {
assert entryExtensionsDataPatchworkClass != null;
return (EntryExtensionsData) entryExtensionsDataPatchworkClass.constructor().invoke(); return (EntryExtensionsData) entryExtensionsDataPatchworkClass.constructor().invoke();
} catch (Throwable e) { } catch (Throwable e) {
throw new IllegalStateException("Failed to construct patchwork class for entry extensions' data", 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 @Override
public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() { public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED); requireSetupPhase(
ConfigContainerSetupPhase.TREE_SETUP,
ConfigContainerSetupPhase.TREE_SEALED,
ConfigContainerSetupPhase.READY
);
return registeredEntryDataExtensions; return registeredEntryDataExtensions;
} }
@@ -149,6 +261,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
public void initialize() { public void initialize() {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED); requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED);
assert rootEntry != null;
rootEntry.visitInOrder(entry -> { rootEntry.visitInOrder(entry -> {
for (TweedExtension extension : extensions()) { for (TweedExtension extension : extensions()) {
extension.initEntry(entry); extension.initEntry(entry);
@@ -160,12 +273,32 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
@Override @Override
public ConfigEntry<T> rootEntry() { public ConfigEntry<T> rootEntry() {
requireSetupPhase(ConfigContainerSetupPhase.TREE_SEALED, ConfigContainerSetupPhase.READY);
assert rootEntry != null;
return rootEntry; return rootEntry;
} }
private void requireSetupPhase(ConfigContainerSetupPhase required) { private void requireSetupPhase(ConfigContainerSetupPhase... allowedPhases) {
if (setupPhase != required) { for (ConfigContainerSetupPhase allowedPhase : allowedPhases) {
throw new IllegalStateException("Config container is not in correct stage, expected " + required + ", but is in " + setupPhase); 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
);
} }
} }

View File

@@ -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;
}
}

View File

@@ -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.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
public interface CommentExtension extends TweedExtension { public interface CommentExtension extends TweedExtension {
Class<? extends CommentExtension> DEFAULT = CommentExtensionImpl.class;
@Nullable String getFullComment(ConfigEntry<?> configEntry); @Nullable String getFullComment(ConfigEntry<?> configEntry);
} }

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.defaultextensions.comment.impl; package de.siphalor.tweed5.defaultextensions.comment.impl;
import com.google.auto.service.AutoService; 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.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
@@ -14,15 +15,21 @@ import de.siphalor.tweed5.defaultextensions.comment.api.*;
import lombok.Getter; import lombok.Getter;
import lombok.Value; import lombok.Value;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@AutoService(CommentExtension.class) @AutoService(CommentExtension.class)
@NullUnmarked
public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension { public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension {
private final ConfigContainer<?> configContainer;
@Getter @Getter
private RegisteredExtensionData<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension; private final RegisteredExtensionData<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension;
private DefaultMiddlewareContainer<CommentProducer> middlewareContainer; 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 @Override
public String getId() { public String getId() {
@@ -30,18 +37,12 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE
} }
@Override @Override
public void setup(TweedExtensionSetupContext context) { public void extensionsFinalized() {
internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class); for (TweedExtension extension : configContainer.extensions()) {
context.registerEntryExtensionData(EntryComment.class);
middlewareContainer = new DefaultMiddlewareContainer<>();
for (TweedExtension extension : context.configContainer().extensions()) {
if (extension instanceof CommentModifyingExtension) { if (extension instanceof CommentModifyingExtension) {
middlewareContainer.register(((CommentModifyingExtension) extension).commentMiddleware()); middlewareContainer.register(((CommentModifyingExtension) extension).commentMiddleware());
} }
} }
middlewareContainer.seal(); middlewareContainer.seal();
} }

View File

@@ -32,8 +32,10 @@ public class PathTracking implements PatherData {
} }
public void popPathPart() { public void popPathPart() {
String poppedPart = pathParts.pop(); if (!pathParts.isEmpty()) {
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1); String poppedPart = pathParts.pop();
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
}
} }
public void pushListContext() { public void pushListContext() {

View File

@@ -1,6 +1,8 @@
package de.siphalor.tweed5.defaultextensions.pather.api; package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
public interface PatherExtension extends TweedExtension { public interface PatherExtension extends TweedExtension {
Class<? extends PatherExtension> DEFAULT = PatherExtensionImpl.class;
} }

View File

@@ -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.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking; import de.siphalor.tweed5.defaultextensions.pather.api.*;
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 lombok.val; import lombok.val;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.NullUnmarked;
@@ -25,7 +22,7 @@ import org.jspecify.annotations.Nullable;
public class PatherExtensionImpl implements PatherExtension, TweedExtension, ReadWriteRelatedExtension { public class PatherExtensionImpl implements PatherExtension, TweedExtension, ReadWriteRelatedExtension {
private static final String PATHER_ID = "pather"; private static final String PATHER_ID = "pather";
private RegisteredExtensionData<ReadWriteContextExtensionsData, PathTracking> rwContextPathTrackingData; private RegisteredExtensionData<ReadWriteContextExtensionsData, PatherData> rwContextPathTrackingData;
private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware; private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware;
private Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware; private Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware;
@@ -36,7 +33,7 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
@Override @Override
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) { public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PathTracking.class); rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PatherData.class);
entryReaderMiddleware = createEntryReaderMiddleware(); entryReaderMiddleware = createEntryReaderMiddleware();
entryWriterMiddleware = createEntryWriterMiddleware(); entryWriterMiddleware = createEntryWriterMiddleware();
@@ -55,7 +52,7 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
val castedInner = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner; val castedInner = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> { 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); 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; val castedInner = (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) -> { 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); castedInner.write(writer, value, entry, context);
return; return;
} }

View File

@@ -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.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
public interface ValidationExtension extends TweedExtension { public interface ValidationExtension extends TweedExtension {
Class<? extends ValidationExtension> DEFAULT = ValidationExtensionImpl.class;
<T extends @Nullable Object> ValidationIssues validate(ConfigEntry<T> entry, T value); <T extends @Nullable Object> ValidationIssues validate(ConfigEntry<T> entry, T value);
} }

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.defaultextensions.validation.impl; package de.siphalor.tweed5.defaultextensions.validation.impl;
import com.google.auto.service.AutoService; 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.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; 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.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor; import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherData; 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.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation; import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension; 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.ValidationIssue;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel; 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 de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value; import lombok.Value;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.*; import java.util.*;
@AutoService(ValidationExtension.class) @AutoService(ValidationExtension.class)
@NullUnmarked
public class ValidationExtensionImpl implements ReadWriteRelatedExtension, ValidationExtension, CommentModifyingExtension { public class ValidationExtensionImpl implements ReadWriteRelatedExtension, ValidationExtension, CommentModifyingExtension {
private static final ValidationResult<?> PRIMITIVE_IS_NULL_RESULT = ValidationResult.withIssues( private static final ValidationResult<?> PRIMITIVE_IS_NULL_RESULT = ValidationResult.withIssues(
null, null,
@@ -74,9 +73,19 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
} }
}; };
private RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension; private final ConfigContainer<?> configContainer;
private MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer; private final RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
private RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues> readContextValidationIssuesExtensionData; 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 @Override
public String getId() { public String getId() {
@@ -84,16 +93,12 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
} }
@Override @Override
public void setup(TweedExtensionSetupContext context) { public void extensionsFinalized() {
context.registerExtension(new PatherExtensionImpl()); for (TweedExtension extension : configContainer.extensions()) {
validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class);
context.registerEntryExtensionData(EntrySpecificValidation.class);
entryValidatorMiddlewareContainer = new DefaultMiddlewareContainer<>();
for (TweedExtension extension : context.configContainer().extensions()) {
if (extension instanceof ValidationProvidingExtension) { if (extension instanceof ValidationProvidingExtension) {
entryValidatorMiddlewareContainer.register(((ValidationProvidingExtension) extension).validationMiddleware()); entryValidatorMiddlewareContainer.register(
((ValidationProvidingExtension) extension).validationMiddleware()
);
} }
} }
entryValidatorMiddlewareContainer.seal(); entryValidatorMiddlewareContainer.seal();

View File

@@ -1,6 +1,8 @@
package de.siphalor.tweed5.defaultextensions.validationfallback.api; package de.siphalor.tweed5.defaultextensions.validationfallback.api;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl;
public interface ValidationFallbackExtension extends TweedExtension { public interface ValidationFallbackExtension extends TweedExtension {
Class<? extends ValidationFallbackExtension> DEFAULT = ValidationFallbackExtensionImpl.class;
} }

View File

@@ -20,14 +20,13 @@ import java.util.stream.Collectors;
@AutoService(ValidationFallbackExtension.class) @AutoService(ValidationFallbackExtension.class)
public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension { public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension {
@Override public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) {
public String getId() { context.registerEntryExtensionData(ValidationFallbackValue.class);
return "validation-fallback";
} }
@Override @Override
public void setup(TweedExtensionSetupContext context) { public String getId() {
context.registerEntryExtensionData(ValidationFallbackValue.class); return "validation-fallback";
} }
@Override @Override

View File

@@ -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.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter; import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters; import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.data.extension.impl.ReadWriteExtensionImpl;
import de.siphalor.tweed5.data.hjson.HjsonCommentType; import de.siphalor.tweed5.data.hjson.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonWriter; import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension; import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer; import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment; import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value; import lombok.Value;
import org.jspecify.annotations.NullUnmarked;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.StringWriter; 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.*; import static org.junit.jupiter.api.Assertions.*;
@NullUnmarked
class CommentExtensionImplTest { class CommentExtensionImplTest {
private DefaultConfigContainer<Map<String, Object>> configContainer; private DefaultConfigContainer<Map<String, Object>> configContainer;
private CommentExtension commentExtension;
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry; private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
private SimpleConfigEntryImpl<Integer> intEntry; private SimpleConfigEntryImpl<Integer> intEntry;
private SimpleConfigEntryImpl<String> stringEntry; private SimpleConfigEntryImpl<String> stringEntry;
private SimpleConfigEntryImpl<Long> noCommentEntry; private SimpleConfigEntryImpl<Long> noCommentEntry;
void setupContainer(Collection<TweedExtension> extraExtensions) { @SafeVarargs
final void setupContainer(Class<? extends TweedExtension>... extraExtensions) {
configContainer = new DefaultConfigContainer<>(); configContainer = new DefaultConfigContainer<>();
commentExtension = new CommentExtensionImpl(); configContainer.registerExtension(CommentExtension.DEFAULT);
configContainer.registerExtension(commentExtension); configContainer.registerExtensions(extraExtensions);
extraExtensions.forEach(configContainer::registerExtension);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
//noinspection unchecked //noinspection unchecked
@@ -68,9 +71,10 @@ class CommentExtensionImplTest {
@Test @Test
void simpleComments() { void simpleComments() {
setupContainer(Collections.emptyList()); setupContainer();
configContainer.initialize(); configContainer.initialize();
CommentExtension commentExtension = configContainer.extension(CommentExtension.class).orElseThrow();
assertEquals("It is an integer", commentExtension.getFullComment(intEntry)); assertEquals("It is an integer", commentExtension.getFullComment(intEntry));
assertEquals("It is a string", commentExtension.getFullComment(stringEntry)); assertEquals("It is a string", commentExtension.getFullComment(stringEntry));
assertNull(commentExtension.getFullComment(noCommentEntry)); assertNull(commentExtension.getFullComment(noCommentEntry));
@@ -78,9 +82,10 @@ class CommentExtensionImplTest {
@Test @Test
void commentProvidingExtension() { void commentProvidingExtension() {
setupContainer(Collections.singletonList(new TestCommentModifyingExtension())); setupContainer(TestCommentModifyingExtension.class);
configContainer.initialize(); 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 an integer\nEND", commentExtension.getFullComment(intEntry));
assertEquals("The comment is:\nIt is a string\nEND", commentExtension.getFullComment(stringEntry)); assertEquals("The comment is:\nIt is a string\nEND", commentExtension.getFullComment(stringEntry));
assertEquals("The comment is:\n\nEND", commentExtension.getFullComment(noCommentEntry)); assertEquals("The comment is:\n\nEND", commentExtension.getFullComment(noCommentEntry));
@@ -88,8 +93,7 @@ class CommentExtensionImplTest {
@Test @Test
void simpleCommentsInHjson() { void simpleCommentsInHjson() {
ReadWriteExtension readWriteExtension = new ReadWriteExtensionImpl(); setupContainer(ReadWriteExtension.DEFAULT);
setupContainer(Collections.singletonList(readWriteExtension));
setupReadWriteTypes(); setupReadWriteTypes();
configContainer.initialize(); configContainer.initialize();
@@ -98,6 +102,7 @@ class CommentExtensionImplTest {
value.put("string", "Hello World"); value.put("string", "Hello World");
value.put("noComment", 567L); value.put("noComment", 567L);
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
StringWriter output = new StringWriter(); StringWriter output = new StringWriter();
assertDoesNotThrow(() -> readWriteExtension.write( assertDoesNotThrow(() -> readWriteExtension.write(
new HjsonWriter(output, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)), new HjsonWriter(output, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)),
@@ -106,14 +111,23 @@ class CommentExtensionImplTest {
readWriteExtension.createReadWriteContextExtensionsData() readWriteExtension.createReadWriteContextExtensionsData()
)); ));
assertEquals("// This is the root value.\n// It is the topmost value in the tree.\n" + assertEquals(
"{\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()); // 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() { private void setupReadWriteTypes() {
//noinspection unchecked //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(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter()));
readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter())); readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter()));
@@ -126,7 +140,8 @@ class CommentExtensionImplTest {
String comment; String comment;
} }
private static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension { @NoArgsConstructor
public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension {
@Override @Override
public String getId() { public String getId() {
return "test-extension"; return "test-extension";
@@ -162,4 +177,4 @@ class CommentExtensionImplTest {
return readerWriter; return readerWriter;
} }
} }
} }

View File

@@ -8,7 +8,6 @@ import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment; 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.EntrySpecificValidation;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
@@ -30,8 +29,6 @@ import static org.junit.jupiter.api.Assertions.*;
class ValidationExtensionImplTest { class ValidationExtensionImplTest {
private DefaultConfigContainer<Map<String, Object>> configContainer; private DefaultConfigContainer<Map<String, Object>> configContainer;
private CommentExtension commentExtension;
private ValidationExtension validationExtension;
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry; private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
private SimpleConfigEntryImpl<Byte> byteEntry; private SimpleConfigEntryImpl<Byte> byteEntry;
private SimpleConfigEntryImpl<Integer> intEntry; private SimpleConfigEntryImpl<Integer> intEntry;
@@ -41,10 +38,8 @@ class ValidationExtensionImplTest {
void setUp() { void setUp() {
configContainer = new DefaultConfigContainer<>(); configContainer = new DefaultConfigContainer<>();
commentExtension = new CommentExtensionImpl(); configContainer.registerExtension(CommentExtension.DEFAULT);
configContainer.registerExtension(commentExtension); configContainer.registerExtension(ValidationExtension.DEFAULT);
validationExtension = new ValidationExtensionImpl();
configContainer.registerExtension(validationExtension);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
//noinspection unchecked //noinspection unchecked
@@ -90,6 +85,7 @@ class ValidationExtensionImplTest {
value.put("int", i); value.put("int", i);
value.put("double", d); value.put("double", d);
ValidationExtension validationExtension = configContainer.extension(ValidationExtension.class).orElseThrow();
ValidationIssues result = validationExtension.validate(rootEntry, value); ValidationIssues result = validationExtension.validate(rootEntry, value);
assertNotNull(result); assertNotNull(result);
assertNotNull(result.issuesByPath()); assertNotNull(result.issuesByPath());
@@ -103,6 +99,7 @@ class ValidationExtensionImplTest {
value.put("int", 124); value.put("int", 124);
value.put("double", 0.2); value.put("double", 0.2);
ValidationExtension validationExtension = configContainer.extension(ValidationExtension.class).orElseThrow();
ValidationIssues result = validationExtension.validate(rootEntry, value); ValidationIssues result = validationExtension.validate(rootEntry, value);
assertNotNull(result); assertNotNull(result);
assertNotNull(result.issuesByPath()); assertNotNull(result.issuesByPath());
@@ -127,4 +124,4 @@ class ValidationExtensionImplTest {
assertEquals(expectedIssue, entryIssues.issues().iterator().next(), "Issue must match"); assertEquals(expectedIssue, entryIssues.issues().iterator().next(), "Issue must match");
} }
} }

View File

@@ -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.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters; 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.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonLexer; import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader; import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.data.hjson.HjsonWriter; import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension; 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.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation; import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; 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.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware; 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.ValidationFallbackExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue; import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@@ -44,24 +42,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
class ValidationFallbackExtensionImplTest { class ValidationFallbackExtensionImplTest {
private DefaultConfigContainer<Integer> configContainer; private DefaultConfigContainer<Integer> configContainer;
private CommentExtension commentExtension;
private ValidationExtension validationExtension;
private ValidationFallbackExtension validationFallbackExtension;
private ReadWriteExtension readWriteExtension;
private SimpleConfigEntryImpl<Integer> intEntry; private SimpleConfigEntryImpl<Integer> intEntry;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@BeforeEach @BeforeEach
void setUp() { void setUp() {
configContainer = new DefaultConfigContainer<>(); configContainer = new DefaultConfigContainer<>();
commentExtension = new CommentExtensionImpl(); configContainer.registerExtension(CommentExtension.DEFAULT);
configContainer.registerExtension(commentExtension); configContainer.registerExtension(ValidationExtension.DEFAULT);
validationExtension = new ValidationExtensionImpl(); configContainer.registerExtension(ValidationFallbackExtension.DEFAULT);
configContainer.registerExtension(validationExtension); configContainer.registerExtension(ReadWriteExtension.DEFAULT);
validationFallbackExtension = new ValidationFallbackExtensionImpl();
configContainer.registerExtension(validationFallbackExtension);
readWriteExtension = new ReadWriteExtensionImpl();
configContainer.registerExtension(readWriteExtension);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
@@ -73,7 +63,7 @@ class ValidationFallbackExtensionImplTest {
entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList( entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList(
new SimpleValidatorMiddleware("non-null", new ConfigEntryValidator() { new SimpleValidatorMiddleware("non-null", new ConfigEntryValidator() {
@Override @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) { if (value == null) {
return ValidationResult.withIssues(null, Collections.singleton( return ValidationResult.withIssues(null, Collections.singleton(
new ValidationIssue("Value must not be null", ValidationIssueLevel.ERROR) new ValidationIssue("Value must not be null", ValidationIssueLevel.ERROR)
@@ -89,7 +79,7 @@ class ValidationFallbackExtensionImplTest {
}), }),
new SimpleValidatorMiddleware("range", new ConfigEntryValidator() { new SimpleValidatorMiddleware("range", new ConfigEntryValidator() {
@Override @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; Integer intValue = (Integer) value;
if (intValue < 1) { if (intValue < 1) {
return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be greater or equal to 1", ValidationIssueLevel.ERROR))); return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be greater or equal to 1", ValidationIssueLevel.ERROR)));
@@ -134,6 +124,7 @@ class ValidationFallbackExtensionImplTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {"0", "7", "123", "null"}) @ValueSource(strings = {"0", "7", "123", "null"})
void fallbackTriggers(String input) { void fallbackTriggers(String input) {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
Integer result = assertDoesNotThrow(() -> readWriteExtension.read( Integer result = assertDoesNotThrow(() -> readWriteExtension.read(
new HjsonReader(new HjsonLexer(new StringReader(input))), new HjsonReader(new HjsonLexer(new StringReader(input))),
intEntry, intEntry,
@@ -144,6 +135,7 @@ class ValidationFallbackExtensionImplTest {
@Test @Test
void description() { void description() {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
assertDoesNotThrow(() -> readWriteExtension.write( assertDoesNotThrow(() -> readWriteExtension.write(
new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)), new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)),
@@ -152,6 +144,13 @@ class ValidationFallbackExtensionImplTest {
readWriteExtension.createReadWriteContextExtensionsData() 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());
} }
} }

View File

@@ -22,9 +22,6 @@ public class PatchworkClassGenerator {
* Class version to use (Java 8) * Class version to use (Java 8)
*/ */
private static final int CLASS_VERSION = Opcodes.V1_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"; 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); super("Invalid patchwork part class " + partClass.getName() + ": " + message);
this.partClass = partClass; this.partClass = partClass;
} }
@Override
public String toString() {
return super.toString();
}
} }
@Value @Value
@@ -636,6 +638,11 @@ public class PatchworkClassGenerator {
stringBuilder.append("\n"); stringBuilder.append("\n");
return stringBuilder.toString(); return stringBuilder.toString();
} }
@Override
public String toString() {
return super.toString();
}
} }
public static class GenerationException extends Exception { public static class GenerationException extends Exception {

View File

@@ -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.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; 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.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
public interface ReadWriteExtension extends TweedExtension { public interface ReadWriteExtension extends TweedExtension {
Class<? extends ReadWriteExtension> DEFAULT = ReadWriteExtensionImpl.class;
void setEntryReaderWriterDefinition(ConfigEntry<?> entry, EntryReaderWriterDefinition readerWriterDefinition); void setEntryReaderWriterDefinition(ConfigEntry<?> entry, EntryReaderWriterDefinition readerWriterDefinition);
ReadWriteContextExtensionsData createReadWriteContextExtensionsData(); ReadWriteContextExtensionsData createReadWriteContextExtensionsData();

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.data.extension.impl; package de.siphalor.tweed5.data.extension.impl;
import com.google.auto.service.AutoService; 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.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData; import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
@@ -24,7 +25,6 @@ import lombok.Setter;
import lombok.Value; import lombok.Value;
import lombok.val; import lombok.val;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
@@ -33,16 +33,27 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
@AutoService(ReadWriteExtension.class) @AutoService(ReadWriteExtension.class)
@NullUnmarked
public class ReadWriteExtensionImpl implements ReadWriteExtension { 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; public ReadWriteExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
private RegisteredExtensionData<EntryExtensionsData, ReadWriteEntryDataExtension> readWriteEntryDataExtension; this.configContainer = configContainer;
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>> entryReaderMiddlewareContainer; this.readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class);
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>> entryWriterMiddlewareContainer; this.readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class);
private Map<Class<?>, RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>> }
readWriteContextExtensionsDataClasses;
private PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork;
@Override @Override
public String getId() { public String getId() {
@@ -50,13 +61,8 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
} }
@Override @Override
public void setup(TweedExtensionSetupContext context) { public void extensionsFinalized() {
readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class); Collection<TweedExtension> extensions = configContainer.extensions();
readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class);
Collection<TweedExtension> extensions = context.configContainer().extensions();
readWriteContextExtensionsDataClasses = new HashMap<>(extensions.size());
ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() { ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() {
@Override @Override
@@ -154,6 +160,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
@Override @Override
public ReadWriteContextExtensionsData createReadWriteContextExtensionsData() { public ReadWriteContextExtensionsData createReadWriteContextExtensionsData() {
try { try {
assert readWriteContextExtensionsDataPatchwork != null;
return (ReadWriteContextExtensionsData) readWriteContextExtensionsDataPatchwork.constructor().invoke(); return (ReadWriteContextExtensionsData) readWriteContextExtensionsDataPatchwork.constructor().invoke();
} catch (Throwable e) { } catch (Throwable e) {
throw new IllegalStateException("Failed to instantiate read write context extensions' data", e); throw new IllegalStateException("Failed to instantiate read write context extensions' data", e);
@@ -161,7 +168,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
} }
@Override @Override
public <T> T read( public <T extends @Nullable Object> T read(
@NonNull TweedDataReader reader, @NonNull TweedDataReader reader,
@NonNull ConfigEntry<T> entry, @NonNull ConfigEntry<T> entry,
@NonNull ReadWriteContextExtensionsData contextExtensionsData @NonNull ReadWriteContextExtensionsData contextExtensionsData
@@ -174,7 +181,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
} }
@Override @Override
public <T> void write( public <T extends @Nullable Object> void write(
@NonNull TweedDataVisitor writer, @NonNull TweedDataVisitor writer,
@Nullable T value, @Nullable T value,
@NonNull ConfigEntry<T> entry, @NonNull ConfigEntry<T> entry,

View File

@@ -27,21 +27,19 @@ import java.io.Writer;
import java.util.*; import java.util.*;
import java.util.function.Function; 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.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
class ReadWriteExtensionImplTest { 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 StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
private ReadWriteExtension readWriteExtension;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@BeforeEach @BeforeEach
void setUp() { void setUp() {
ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>(); configContainer.registerExtension(ReadWriteExtension.DEFAULT);
readWriteExtension = new ReadWriteExtensionImpl();
configContainer.registerExtension(readWriteExtension);
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>) (Class<?>) Map.class), LinkedHashMap::new); rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>) (Class<?>) Map.class), LinkedHashMap::new);
@@ -73,6 +71,7 @@ class ReadWriteExtensionImplTest {
value.put("int", 123); value.put("int", 123);
value.put("list", Arrays.asList(true, false, true)); value.put("list", Arrays.asList(true, false, true));
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
assertDoesNotThrow(() -> readWriteExtension.write( assertDoesNotThrow(() -> readWriteExtension.write(
setupWriter(writer -> new HjsonWriter(writer, new HjsonWriter.Options())), setupWriter(writer -> new HjsonWriter(writer, new HjsonWriter.Options())),
value, value,
@@ -80,13 +79,33 @@ class ReadWriteExtensionImplTest {
readWriteExtension.createReadWriteContextExtensionsData() 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 @Test
void read() { void read() {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
Map<String, Object> result = assertDoesNotThrow(() -> readWriteExtension.read( 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, rootEntry,
readWriteExtension.createReadWriteContextExtensionsData() readWriteExtension.createReadWriteContextExtensionsData()
)); ));
@@ -97,7 +116,6 @@ class ReadWriteExtensionImplTest {
} }
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) { private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {
stringWriter = new StringWriter();
return writerFactory.apply(stringWriter); return writerFactory.apply(stringWriter);
} }

View File

@@ -68,7 +68,7 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor
return; return;
} }
ReadWriteExtension readWriteExtension = context.configContainer().extension(ReadWriteExtension.class); ReadWriteExtension readWriteExtension = context.configContainer().extension(ReadWriteExtension.class).orElse(null);
if (readWriteExtension == null) { if (readWriteExtension == null) {
log.error("You must not use {} without the {}", this.getClass().getSimpleName(), ReadWriteExtension.class.getSimpleName()); log.error("You must not use {} without the {}", this.getClass().getSimpleName(), ReadWriteExtension.class.getSimpleName());
return; return;

View File

@@ -32,8 +32,7 @@ class WeaverPojoSerdeExtensionTest {
ConfigContainer<AnnotatedConfig> configContainer = weaverBootstrapper.weave(); ConfigContainer<AnnotatedConfig> configContainer = weaverBootstrapper.weave();
configContainer.initialize(); configContainer.initialize();
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class); ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
assertThat(readWriteExtension).isNotNull();
AnnotatedConfig config = new AnnotatedConfig(123, "test", new TestClass(987)); AnnotatedConfig config = new AnnotatedConfig(123, "test", new TestClass(987));

View File

@@ -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.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator; import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
import de.siphalor.tweed5.patchwork.impl.PatchworkClass; import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator; 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 de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
import lombok.*; import lombok.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.Nullable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
@@ -44,21 +42,12 @@ public class TweedPojoWeaverBootstrapper<T> {
Collection<TweedPojoWeaver> weavers = loadWeavers(Arrays.asList(rootWeavingConfig.weavers())); Collection<TweedPojoWeaver> weavers = loadWeavers(Arrays.asList(rootWeavingConfig.weavers()));
Collection<TweedPojoWeavingPostProcessor> postProcessors = loadPostProcessors(Arrays.asList(rootWeavingConfig.postProcessors())); Collection<TweedPojoWeavingPostProcessor> postProcessors = loadPostProcessors(Arrays.asList(rootWeavingConfig.postProcessors()));
Collection<TweedExtension> extensions = loadExtensions(Arrays.asList(rootWeavingConfig.extensions())); configContainer.registerExtensions(rootWeavingConfig.extensions());
configContainer.registerExtensions(extensions.toArray(new TweedExtension[0]));
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
return new TweedPojoWeaverBootstrapper<>(pojoClass, configContainer, weavers, postProcessors); 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) { private static Collection<TweedPojoWeaver> loadWeavers(Collection<Class<? extends TweedPojoWeaver>> weaverClasses) {
return weaverClasses.stream() return weaverClasses.stream()
.map(weaverClass -> TweedPojoWeaver.FACTORY.construct(weaverClass).finish()) .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) { private static <A extends Annotation> A expectAnnotation(Class<?> clazz, Class<A> annotationClass) {
A annotation = clazz.getAnnotation(annotationClass); A annotation = clazz.getAnnotation(annotationClass);
if (annotation == null) { if (annotation == null) {

View File

@@ -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.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry; import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData; 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.namingformat.api.NamingFormat;
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry; 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 lombok.AllArgsConstructor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullUnmarked;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith; import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
@@ -20,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@NullUnmarked
class CompoundPojoWeaverTest { class CompoundPojoWeaverTest {
@Test @Test
@@ -39,10 +42,7 @@ class CompoundPojoWeaverTest {
if (entry != null) { if (entry != null) {
return entry; return entry;
} else { } else {
//noinspection unchecked return new SimpleConfigEntryImpl<>(valueType.declaredType());
ConfigEntry<T> configEntry = mock((Class<SimpleConfigEntry<T>>) (Class<?>) SimpleConfigEntry.class);
when(configEntry.valueClass()).thenReturn(valueType.declaredType());
return configEntry;
} }
} }
}, mock(ConfigContainer.class)) }, mock(ConfigContainer.class))