[patchwork, core, extensions] Hugely simplify Patchworks

This commit is contained in:
2025-06-13 22:04:16 +02:00
parent 694f993b8c
commit 6e5c9a23c2
72 changed files with 987 additions and 1977 deletions

View File

@@ -1,13 +1,12 @@
package de.siphalor.tweed5.core.api.container;
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
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.Patchwork;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
/**
@@ -16,7 +15,7 @@ import java.util.Optional;
* @param <T> The class that the config tree represents
* @see ConfigContainerSetupPhase
*/
public interface ConfigContainer<T> {
public interface ConfigContainer<T extends @Nullable Object> {
@SuppressWarnings("rawtypes")
TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build();
@@ -36,10 +35,9 @@ public interface ConfigContainer<T> {
void attachTree(ConfigEntry<T> rootEntry);
EntryExtensionsData createExtensionsData();
Patchwork createExtensionsData();
void initialize();
ConfigEntry<T> rootEntry();
Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions();
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphalor.tweed5.core.api.container;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,14 +1,14 @@
package de.siphalor.tweed5.core.api.entry;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import lombok.Getter;
@Getter
public abstract class BaseConfigEntry<T> implements ConfigEntry<T> {
private final ConfigContainer<?> container;
private final Class<T> valueClass;
private final EntryExtensionsData extensionsData;
private final Patchwork extensionsData;
public BaseConfigEntry(ConfigContainer<?> container, Class<T> valueClass) {
this.container = container;

View File

@@ -1,8 +1,15 @@
package de.siphalor.tweed5.core.api.entry;
import java.util.Collection;
import java.util.function.Consumer;
public interface CollectionConfigEntry<E, T extends Collection<E>> extends ConfigEntry<T> {
@Override
default CollectionConfigEntry<E, T> apply(Consumer<ConfigEntry<T>> function) {
ConfigEntry.super.apply(function);
return this;
}
ConfigEntry<E> elementEntry();
T instantiateCollection(int size);

View File

@@ -1,8 +1,15 @@
package de.siphalor.tweed5.core.api.entry;
import java.util.Map;
import java.util.function.Consumer;
public interface CompoundConfigEntry<T> extends ConfigEntry<T> {
@Override
default CompoundConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
ConfigEntry.super.apply(function);
return this;
}
Map<String, ConfigEntry<?>> subEntries();
<V> void set(T compoundValue, String key, V value);

View File

@@ -1,18 +1,32 @@
package de.siphalor.tweed5.core.api.entry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
public interface ConfigEntry<T> {
import java.util.function.Consumer;
import java.util.function.Function;
public interface ConfigEntry<T extends @Nullable Object> {
ConfigContainer<?> container();
Class<T> valueClass();
EntryExtensionsData extensionsData();
Patchwork extensionsData();
default ConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
function.accept(this);
return this;
}
default <R> R call(Function<ConfigEntry<T>, R> function) {
return function.apply(this);
}
void visitInOrder(ConfigEntryVisitor visitor);
void visitInOrder(ConfigEntryValueVisitor visitor, T value);
void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value);
T deepCopy(T value);
T deepCopy(@NonNull T value);
}

View File

@@ -1,4 +1,11 @@
package de.siphalor.tweed5.core.api.entry;
import java.util.function.Consumer;
public interface SimpleConfigEntry<T> extends ConfigEntry<T> {
@Override
default SimpleConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
ConfigEntry.super.apply(function);
return this;
}
}

View File

@@ -1,6 +0,0 @@
package de.siphalor.tweed5.core.api.extension;
import de.siphalor.tweed5.patchwork.api.Patchwork;
public interface EntryExtensionsData extends Patchwork<EntryExtensionsData> {
}

View File

@@ -1,7 +0,0 @@
package de.siphalor.tweed5.core.api.extension;
import de.siphalor.tweed5.patchwork.api.Patchwork;
public interface RegisteredExtensionData<U extends Patchwork<U>, E> {
void set(U patchwork, E extension);
}

View File

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

View File

@@ -3,21 +3,15 @@ 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.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;
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.utils.api.collection.InheritanceMap;
import lombok.Getter;
import lombok.Setter;
import org.jspecify.annotations.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
@@ -28,9 +22,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
private final Set<Class<? extends TweedExtension>> requestedExtensions = new HashSet<>();
private final InheritanceMap<TweedExtension> extensions = new InheritanceMap<>(TweedExtension.class);
private @Nullable ConfigEntry<T> rootEntry;
private @Nullable PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
private final Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions
= new HashMap<>();
private @Nullable PatchworkFactory entryExtensionsDataPatchworkFactory;
@Override
public void registerExtension(Class<? extends TweedExtension> extensionClass) {
@@ -42,15 +34,12 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
public void finishExtensionSetup() {
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
PatchworkFactory.Builder entryExtensionDataPatchworkFactoryBuilder = PatchworkFactory.builder();
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
@Override
public <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass) {
if (registeredEntryDataExtensions.containsKey(dataClass)) {
throw new IllegalArgumentException("Extension " + dataClass.getName() + " is already registered");
}
RegisteredExtensionDataImpl<EntryExtensionsData, E> registered = new RegisteredExtensionDataImpl<>();
registeredEntryDataExtensions.put(dataClass, registered);
return registered;
public <E> PatchworkPartAccess<E> registerEntryExtensionData(Class<E> dataClass) {
return entryExtensionDataPatchworkFactoryBuilder.registerPart(dataClass);
}
@Override
@@ -81,20 +70,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
}
}
PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()
.patchworkInterface(EntryExtensionsData.class)
.classPackage("de.siphalor.tweed5.core.generated.entryextensiondata")
.classPrefix("EntryExtensionsData$")
.build();
try {
entryExtensionsDataPatchworkClass = entryExtensionsDataGenerator.createClass(registeredEntryDataExtensions.keySet());
for (PatchworkClassPart part : entryExtensionsDataPatchworkClass.parts()) {
RegisteredExtensionDataImpl<EntryExtensionsData, ?> registeredExtension = registeredEntryDataExtensions.get(part.partInterface());
registeredExtension.setter(part.fieldSetter());
}
} catch (PatchworkClassGenerator.GenerationException e) {
throw new IllegalStateException("Failed to create patchwork class for entry extensions' data", e);
}
entryExtensionsDataPatchworkFactory = entryExtensionDataPatchworkFactoryBuilder.build();
setupPhase = ConfigContainerSetupPhase.TREE_SETUP;
@@ -230,25 +206,11 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
}
@Override
public EntryExtensionsData createExtensionsData() {
public Patchwork createExtensionsData() {
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);
}
}
@Override
public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() {
requireSetupPhase(
ConfigContainerSetupPhase.TREE_SETUP,
ConfigContainerSetupPhase.TREE_ATTACHED,
ConfigContainerSetupPhase.INITIALIZED
);
return registeredEntryDataExtensions;
assert entryExtensionsDataPatchworkFactory != null;
return entryExtensionsDataPatchworkFactory.create();
}
@Override
@@ -295,18 +257,4 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
);
}
}
@Setter
private static class RegisteredExtensionDataImpl<U extends Patchwork<U>, E> implements RegisteredExtensionData<U, E> {
private MethodHandle setter;
@Override
public void set(U patchwork, E extension) {
try {
setter.invokeWithArguments(patchwork, extension);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import org.jspecify.annotations.NonNull;
public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements SimpleConfigEntry<T> {
public SimpleConfigEntryImpl(ConfigContainer<?> container, Class<T> valueClass) {
@@ -22,7 +23,7 @@ public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements Simp
}
@Override
public T deepCopy(T value) {
public T deepCopy(@NonNull T value) {
return value;
}
}

View File

@@ -2,8 +2,6 @@ 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;
@@ -131,28 +129,7 @@ class DefaultConfigContainerTest {
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");
assertThat(extensionData).isNotNull();
}
@SuppressWarnings("unchecked")