[patchwork, core, extensions] Hugely simplify Patchworks
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
assertj = "3.26.3"
|
assertj = "3.26.3"
|
||||||
asm = "9.7"
|
|
||||||
autoservice = "1.1.1"
|
autoservice = "1.1.1"
|
||||||
java-main = "8"
|
java-main = "8"
|
||||||
java-test = "21"
|
java-test = "21"
|
||||||
@@ -16,8 +15,6 @@ lombok = { id = "io.freefair.lombok", version = "8.13.1" }
|
|||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }
|
assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }
|
||||||
asm-commons = { group = "org.ow2.asm", name = "asm-commons", version.ref = "asm" }
|
|
||||||
asm-core = { group = "org.ow2.asm", name = "asm", version.ref = "asm" }
|
|
||||||
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
|
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
|
||||||
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
||||||
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package de.siphalor.tweed5.core.api.container;
|
package de.siphalor.tweed5.core.api.container;
|
||||||
|
|
||||||
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
|
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.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,7 +15,7 @@ import java.util.Optional;
|
|||||||
* @param <T> The class that the config tree represents
|
* @param <T> The class that the config tree represents
|
||||||
* @see ConfigContainerSetupPhase
|
* @see ConfigContainerSetupPhase
|
||||||
*/
|
*/
|
||||||
public interface ConfigContainer<T> {
|
public interface ConfigContainer<T extends @Nullable Object> {
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build();
|
TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build();
|
||||||
|
|
||||||
@@ -36,10 +35,9 @@ public interface ConfigContainer<T> {
|
|||||||
|
|
||||||
void attachTree(ConfigEntry<T> rootEntry);
|
void attachTree(ConfigEntry<T> rootEntry);
|
||||||
|
|
||||||
EntryExtensionsData createExtensionsData();
|
Patchwork createExtensionsData();
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
ConfigEntry<T> rootEntry();
|
ConfigEntry<T> rootEntry();
|
||||||
Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@NullMarked
|
||||||
|
package de.siphalor.tweed5.core.api.container;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public abstract class BaseConfigEntry<T> implements ConfigEntry<T> {
|
public abstract class BaseConfigEntry<T> implements ConfigEntry<T> {
|
||||||
private final ConfigContainer<?> container;
|
private final ConfigContainer<?> container;
|
||||||
private final Class<T> valueClass;
|
private final Class<T> valueClass;
|
||||||
private final EntryExtensionsData extensionsData;
|
private final Patchwork extensionsData;
|
||||||
|
|
||||||
public BaseConfigEntry(ConfigContainer<?> container, Class<T> valueClass) {
|
public BaseConfigEntry(ConfigContainer<?> container, Class<T> valueClass) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface CollectionConfigEntry<E, T extends Collection<E>> extends ConfigEntry<T> {
|
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();
|
ConfigEntry<E> elementEntry();
|
||||||
|
|
||||||
T instantiateCollection(int size);
|
T instantiateCollection(int size);
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface CompoundConfigEntry<T> extends ConfigEntry<T> {
|
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();
|
Map<String, ConfigEntry<?>> subEntries();
|
||||||
|
|
||||||
<V> void set(T compoundValue, String key, V value);
|
<V> void set(T compoundValue, String key, V value);
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
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.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();
|
ConfigContainer<?> container();
|
||||||
|
|
||||||
Class<T> valueClass();
|
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(ConfigEntryVisitor visitor);
|
||||||
void visitInOrder(ConfigEntryValueVisitor visitor, T value);
|
void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value);
|
||||||
|
|
||||||
T deepCopy(T value);
|
T deepCopy(@NonNull T value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
package de.siphalor.tweed5.core.api.entry;
|
package de.siphalor.tweed5.core.api.entry;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface SimpleConfigEntry<T> extends ConfigEntry<T> {
|
public interface SimpleConfigEntry<T> extends ConfigEntry<T> {
|
||||||
|
@Override
|
||||||
|
default SimpleConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
|
||||||
|
ConfigEntry.super.apply(function);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package de.siphalor.tweed5.core.api.extension;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
|
||||||
|
|
||||||
public interface EntryExtensionsData extends Patchwork<EntryExtensionsData> {
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package de.siphalor.tweed5.core.api.extension;
|
package de.siphalor.tweed5.core.api.extension;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
|
||||||
public interface TweedExtensionSetupContext {
|
public interface TweedExtensionSetupContext {
|
||||||
<E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass);
|
<E> PatchworkPartAccess<E> registerEntryExtensionData(Class<E> dataClass);
|
||||||
void registerExtension(Class<? extends TweedExtension> extensionClass);
|
void registerExtension(Class<? extends TweedExtension> extensionClass);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.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.EntryExtensionsData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
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.PatchworkFactory;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
|
||||||
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 org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
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 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 @Nullable ConfigEntry<T> rootEntry;
|
private @Nullable ConfigEntry<T> rootEntry;
|
||||||
private @Nullable PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
private @Nullable PatchworkFactory entryExtensionsDataPatchworkFactory;
|
||||||
private final Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions
|
|
||||||
= new HashMap<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerExtension(Class<? extends TweedExtension> extensionClass) {
|
public void registerExtension(Class<? extends TweedExtension> extensionClass) {
|
||||||
@@ -42,15 +34,12 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
|||||||
public void finishExtensionSetup() {
|
public void finishExtensionSetup() {
|
||||||
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
||||||
|
|
||||||
|
PatchworkFactory.Builder entryExtensionDataPatchworkFactoryBuilder = PatchworkFactory.builder();
|
||||||
|
|
||||||
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
|
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
|
||||||
@Override
|
@Override
|
||||||
public <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass) {
|
public <E> PatchworkPartAccess<E> registerEntryExtensionData(Class<E> dataClass) {
|
||||||
if (registeredEntryDataExtensions.containsKey(dataClass)) {
|
return entryExtensionDataPatchworkFactoryBuilder.registerPart(dataClass);
|
||||||
throw new IllegalArgumentException("Extension " + dataClass.getName() + " is already registered");
|
|
||||||
}
|
|
||||||
RegisteredExtensionDataImpl<EntryExtensionsData, E> registered = new RegisteredExtensionDataImpl<>();
|
|
||||||
registeredEntryDataExtensions.put(dataClass, registered);
|
|
||||||
return registered;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,20 +70,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()
|
entryExtensionsDataPatchworkFactory = entryExtensionDataPatchworkFactoryBuilder.build();
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPhase = ConfigContainerSetupPhase.TREE_SETUP;
|
setupPhase = ConfigContainerSetupPhase.TREE_SETUP;
|
||||||
|
|
||||||
@@ -230,25 +206,11 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntryExtensionsData createExtensionsData() {
|
public Patchwork createExtensionsData() {
|
||||||
requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP);
|
requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP);
|
||||||
|
|
||||||
try {
|
assert entryExtensionsDataPatchworkFactory != null;
|
||||||
assert entryExtensionsDataPatchworkClass != null;
|
return entryExtensionsDataPatchworkFactory.create();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.ConfigEntryValueVisitor;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||||
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
|
||||||
public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements SimpleConfigEntry<T> {
|
public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements SimpleConfigEntry<T> {
|
||||||
public SimpleConfigEntryImpl(ConfigContainer<?> container, Class<T> valueClass) {
|
public SimpleConfigEntryImpl(ConfigContainer<?> container, Class<T> valueClass) {
|
||||||
@@ -22,7 +23,7 @@ public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements Simp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T deepCopy(T value) {
|
public T deepCopy(@NonNull T value) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package de.siphalor.tweed5.core.impl;
|
|||||||
|
|
||||||
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.EntryExtensionsData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
@@ -131,28 +129,7 @@ class DefaultConfigContainerTest {
|
|||||||
configContainer.registerExtension(ExtensionB.class);
|
configContainer.registerExtension(ExtensionB.class);
|
||||||
configContainer.finishExtensionSetup();
|
configContainer.finishExtensionSetup();
|
||||||
var extensionData = configContainer.createExtensionsData();
|
var extensionData = configContainer.createExtensionsData();
|
||||||
assertThat(extensionData).isNotNull()
|
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
@@ -5,8 +5,20 @@ import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
|||||||
import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl;
|
import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface CommentExtension extends TweedExtension {
|
public interface CommentExtension extends TweedExtension {
|
||||||
Class<? extends CommentExtension> DEFAULT = CommentExtensionImpl.class;
|
Class<? extends CommentExtension> DEFAULT = CommentExtensionImpl.class;
|
||||||
|
|
||||||
|
static <C extends ConfigEntry<?>> Consumer<C> baseComment(String baseComment) {
|
||||||
|
return entry -> {
|
||||||
|
CommentExtension extension = entry.container().extension(CommentExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No comment extension registered"));
|
||||||
|
extension.setBaseComment(entry, baseComment);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBaseComment(ConfigEntry<?> configEntry, String baseComment);
|
||||||
|
|
||||||
@Nullable String getFullComment(ConfigEntry<?> configEntry);
|
@Nullable String getFullComment(ConfigEntry<?> configEntry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.comment.api;
|
|
||||||
|
|
||||||
public interface EntryComment {
|
|
||||||
String comment();
|
|
||||||
}
|
|
||||||
@@ -2,32 +2,32 @@ 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.container.ConfigContainer;
|
||||||
|
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.EntryExtensionsData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||||
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
|
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
|
||||||
import de.siphalor.tweed5.defaultextensions.comment.api.*;
|
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
|
||||||
import org.jspecify.annotations.NonNull;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
@AutoService(CommentExtension.class)
|
@AutoService(CommentExtension.class)
|
||||||
public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension {
|
public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension {
|
||||||
private final ConfigContainer<?> configContainer;
|
private final ConfigContainer<?> configContainer;
|
||||||
@Getter
|
@Getter
|
||||||
private final RegisteredExtensionData<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension;
|
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||||
private final DefaultMiddlewareContainer<CommentProducer> middlewareContainer;
|
private final DefaultMiddlewareContainer<CommentProducer> middlewareContainer;
|
||||||
|
|
||||||
public CommentExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
public CommentExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
||||||
this.configContainer = configContainer;
|
this.configContainer = configContainer;
|
||||||
this.internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class);
|
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||||
context.registerEntryExtensionData(EntryComment.class);
|
|
||||||
this.middlewareContainer = new DefaultMiddlewareContainer<>();
|
this.middlewareContainer = new DefaultMiddlewareContainer<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,31 +48,49 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware() {
|
public @Nullable Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware() {
|
||||||
return TweedEntryWriterCommentMiddleware.INSTANCE;
|
return new TweedEntryWriterCommentMiddleware(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBaseComment(ConfigEntry<?> configEntry, String baseComment) {
|
||||||
|
if (configEntry.container() != configContainer) {
|
||||||
|
throw new IllegalArgumentException("config entry doesn't belong to config container of this extension");
|
||||||
|
} else if (configContainer.setupPhase().compareTo(ConfigContainerSetupPhase.INITIALIZED) >= 0) {
|
||||||
|
throw new IllegalStateException("config container must not be initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrCreateCustomEntryData(configEntry).baseComment(baseComment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initEntry(ConfigEntry<?> configEntry) {
|
public void initEntry(ConfigEntry<?> configEntry) {
|
||||||
EntryExtensionsData entryExtensionsData = configEntry.extensionsData();
|
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
|
||||||
String baseComment;
|
entryData.commentProducer(middlewareContainer.process(entry -> entryData.baseComment()));
|
||||||
if (entryExtensionsData.isPatchworkPartSet(EntryComment.class)) {
|
}
|
||||||
baseComment = ((EntryComment) entryExtensionsData).comment();
|
|
||||||
} else {
|
|
||||||
baseComment = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
CommentProducer middleware = middlewareContainer.process(entry -> baseComment);
|
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
|
||||||
internalEntryDataExtension.set(entryExtensionsData, new InternalCommentEntryDataImpl(middleware));
|
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
|
||||||
|
if (customEntryData == null) {
|
||||||
|
customEntryData = new CustomEntryData();
|
||||||
|
entry.extensionsData().set(customEntryDataAccess, customEntryData);
|
||||||
|
}
|
||||||
|
return customEntryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable String getFullComment(@NonNull ConfigEntry<?> configEntry) {
|
public @Nullable String getFullComment(ConfigEntry<?> configEntry) {
|
||||||
String comment = ((InternalCommentEntryData) configEntry.extensionsData()).commentProducer().createComment(configEntry);
|
CustomEntryData customEntryData = configEntry.extensionsData().get(customEntryDataAccess);
|
||||||
|
if (customEntryData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String comment = customEntryData.commentProducer().createComment(configEntry);
|
||||||
return comment.isEmpty() ? null : comment;
|
return comment.isEmpty() ? null : comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
|
||||||
private static class InternalCommentEntryDataImpl implements InternalCommentEntryData {
|
@Data
|
||||||
CommentProducer commentProducer;
|
private static class CustomEntryData {
|
||||||
|
private String baseComment = "";
|
||||||
|
private CommentProducer commentProducer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.comment.impl;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
|
|
||||||
|
|
||||||
public interface InternalCommentEntryData {
|
|
||||||
CommentProducer commentProducer();
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,14 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
|||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jspecify.annotations.NonNull;
|
import org.jspecify.annotations.NonNull;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?, ?>> {
|
class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?, ?>> {
|
||||||
public static final TweedEntryWriterCommentMiddleware INSTANCE = new TweedEntryWriterCommentMiddleware();
|
private final CommentExtension commentExtension;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
@@ -49,7 +51,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
private static class CompoundDataVisitor implements TweedDataVisitor {
|
private class CompoundDataVisitor implements TweedDataVisitor {
|
||||||
private final TweedDataVisitor delegate;
|
private final TweedDataVisitor delegate;
|
||||||
private final CompoundConfigEntry<?> compoundConfigEntry;
|
private final CompoundConfigEntry<?> compoundConfigEntry;
|
||||||
|
|
||||||
@@ -134,11 +136,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable String getEntryComment(ConfigEntry<?> entry) {
|
private @Nullable String getEntryComment(ConfigEntry<?> entry) {
|
||||||
if (!entry.extensionsData().isPatchworkPartSet(InternalCommentEntryData.class)) {
|
return commentExtension.getFullComment(entry);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String comment = ((InternalCommentEntryData) entry.extensionsData()).commentProducer().createComment(entry).trim();
|
|
||||||
return comment.isEmpty() ? null : comment;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.jspecify.annotations.Nullable;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
|
||||||
public class PathTracking implements PatherData {
|
public class PathTracking {
|
||||||
private final StringBuilder pathBuilder = new StringBuilder(256);
|
private final StringBuilder pathBuilder = new StringBuilder(256);
|
||||||
private final Deque<Context> contextStack = new ArrayDeque<>(50);
|
private final Deque<Context> contextStack = new ArrayDeque<>(50);
|
||||||
private final Deque<String> pathParts = new ArrayDeque<>(50);
|
private final Deque<String> pathParts = new ArrayDeque<>(50);
|
||||||
@@ -52,8 +52,7 @@ public class PathTracking implements PatherData {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String currentPath() {
|
||||||
public String valuePath() {
|
|
||||||
return pathBuilder.toString();
|
return pathBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.pather.api;
|
|||||||
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 lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor {
|
public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor {
|
||||||
@@ -10,7 +11,7 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
|
|||||||
private final PathTracking pathTracking;
|
private final PathTracking pathTracking;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void visitEntry(ConfigEntry<T> entry, T value) {
|
public <T extends @Nullable Object> void visitEntry(ConfigEntry<T> entry, T value) {
|
||||||
delegate.visitEntry(entry, value);
|
delegate.visitEntry(entry, value);
|
||||||
entryVisited();
|
entryVisited();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.pather.api;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension data for {@link de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData}
|
|
||||||
* that provides the path to the value currently being read/written.
|
|
||||||
*/
|
|
||||||
public interface PatherData {
|
|
||||||
String valuePath();
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
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.data.extension.api.TweedReadContext;
|
||||||
|
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
|
||||||
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
|
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
|
||||||
|
|
||||||
public interface PatherExtension extends TweedExtension {
|
public interface PatherExtension extends TweedExtension {
|
||||||
Class<? extends PatherExtension> DEFAULT = PatherExtensionImpl.class;
|
Class<? extends PatherExtension> DEFAULT = PatherExtensionImpl.class;
|
||||||
|
|
||||||
|
String getPath(TweedReadContext context);
|
||||||
|
String getPath(TweedWriteContext context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,33 @@ package de.siphalor.tweed5.defaultextensions.pather.impl;
|
|||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
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.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
import de.siphalor.tweed5.data.extension.api.*;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
|
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
|
||||||
|
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
|
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
|
||||||
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.*;
|
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataReader;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataVisitor;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
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;
|
||||||
|
|
||||||
@AutoService(PatherExtension.class)
|
@AutoService(PatherExtension.class)
|
||||||
@NullUnmarked
|
|
||||||
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, PatherData> rwContextPathTrackingData;
|
private final Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware = createEntryReaderMiddleware();
|
||||||
private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware;
|
private final Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware = createEntryWriterMiddleware();
|
||||||
private Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware;
|
|
||||||
|
private @Nullable PatchworkPartAccess<PathTracking> rwContextPathTrackingAccess;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -33,13 +37,32 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
||||||
rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PatherData.class);
|
rwContextPathTrackingAccess = context.registerReadWriteContextExtensionData(PathTracking.class);
|
||||||
|
|
||||||
entryReaderMiddleware = createEntryReaderMiddleware();
|
|
||||||
entryWriterMiddleware = createEntryWriterMiddleware();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull Middleware<TweedEntryReader<?, ?>> createEntryReaderMiddleware() {
|
@Override
|
||||||
|
public String getPath(TweedReadContext context) {
|
||||||
|
assert rwContextPathTrackingAccess != null;
|
||||||
|
|
||||||
|
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
|
||||||
|
if (pathTracking == null) {
|
||||||
|
throw new IllegalStateException("Path tracking is not active!");
|
||||||
|
}
|
||||||
|
return pathTracking.currentPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath(TweedWriteContext context) {
|
||||||
|
assert rwContextPathTrackingAccess != null;
|
||||||
|
|
||||||
|
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
|
||||||
|
if (pathTracking == null) {
|
||||||
|
throw new IllegalStateException("Path tracking is not active!");
|
||||||
|
}
|
||||||
|
return pathTracking.currentPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Middleware<TweedEntryReader<?, ?>> createEntryReaderMiddleware() {
|
||||||
return new Middleware<TweedEntryReader<?, ?>>() {
|
return new Middleware<TweedEntryReader<?, ?>>() {
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
@@ -48,16 +71,19 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
||||||
|
assert rwContextPathTrackingAccess != null;
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
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(PatherData.class)) {
|
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
|
||||||
|
if (pathTracking != null) {
|
||||||
return castedInner.read(reader, entry, context);
|
return castedInner.read(reader, entry, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
PathTracking pathTracking = new PathTracking();
|
pathTracking = new PathTracking();
|
||||||
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
|
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
|
||||||
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
|
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -73,17 +99,20 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
|
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
|
||||||
|
assert rwContextPathTrackingAccess != null;
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
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(PatherData.class)) {
|
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
|
||||||
|
if (pathTracking != null) {
|
||||||
castedInner.write(writer, value, entry, context);
|
castedInner.write(writer, value, entry, context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PathTracking pathTracking = new PathTracking();
|
pathTracking = new PathTracking();
|
||||||
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
|
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
|
||||||
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context);
|
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.validation.api;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface EntrySpecificValidation {
|
|
||||||
Collection<Middleware<ConfigEntryValidator>> validators();
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,54 @@ 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.core.api.middleware.Middleware;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
|
||||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
|
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
|
||||||
import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl;
|
import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public interface ValidationExtension extends TweedExtension {
|
public interface ValidationExtension extends TweedExtension {
|
||||||
Class<? extends ValidationExtension> DEFAULT = ValidationExtensionImpl.class;
|
Class<? extends ValidationExtension> DEFAULT = ValidationExtensionImpl.class;
|
||||||
|
|
||||||
|
static <C extends ConfigEntry<T>, T> Consumer<C> validators(ConfigEntryValidator... validators) {
|
||||||
|
return entry -> {
|
||||||
|
ValidationExtension extension = entry.container().extension(ValidationExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No validation extension registered"));
|
||||||
|
extension.addValidators(entry, validators);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <C extends ConfigEntry<T>, T> Function<C, ValidationIssues> validate(T value) {
|
||||||
|
return entry -> {
|
||||||
|
ValidationExtension extension = entry.container().extension(ValidationExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No validation extension registered"));
|
||||||
|
return extension.validate(entry, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> void addValidators(ConfigEntry<T> entry, ConfigEntryValidator... validators) {
|
||||||
|
String lastId = null;
|
||||||
|
for (ConfigEntryValidator validator : validators) {
|
||||||
|
String id = UUID.randomUUID().toString();
|
||||||
|
Set<String> mustComeAfter = lastId == null ? Collections.emptySet() : Collections.singleton(lastId);
|
||||||
|
|
||||||
|
addValidatorMiddleware(entry, new SimpleValidatorMiddleware(id, validator) {
|
||||||
|
@Override
|
||||||
|
public Set<String> mustComeAfter() {
|
||||||
|
return mustComeAfter;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lastId = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<T> void addValidatorMiddleware(ConfigEntry<T> entry, Middleware<ConfigEntryValidator> validator);
|
||||||
|
|
||||||
<T extends @Nullable Object> ValidationIssues validate(ConfigEntry<T> entry, T value);
|
<T extends @Nullable Object> ValidationIssues validate(ConfigEntry<T> entry, T value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.validation.api.result;
|
|||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -11,16 +12,16 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class ValidationResult<T> {
|
public class ValidationResult<T extends @Nullable Object> {
|
||||||
private final T value;
|
private final T value;
|
||||||
private final Collection<ValidationIssue> issues;
|
private final Collection<ValidationIssue> issues;
|
||||||
private final boolean hasError;
|
private final boolean hasError;
|
||||||
|
|
||||||
public static <T> ValidationResult<T> ok(T value) {
|
public static <T extends @Nullable Object> ValidationResult<T> ok(T value) {
|
||||||
return new ValidationResult<>(value, Collections.emptyList(), false);
|
return new ValidationResult<>(value, Collections.emptyList(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> ValidationResult<T> withIssues(T value, Collection<ValidationIssue> issues) {
|
public static <T extends @Nullable Object> ValidationResult<T> withIssues(T value, Collection<ValidationIssue> issues) {
|
||||||
return new ValidationResult<>(value, issues, issuesContainError(issues));
|
return new ValidationResult<>(value, issues, issuesContainError(issues));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.validation.impl;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
|
|
||||||
|
|
||||||
public interface InternalValidationEntryData {
|
|
||||||
ConfigEntryValidator completeEntryValidator();
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,6 @@ import com.google.auto.service.AutoService;
|
|||||||
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.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.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||||
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
||||||
@@ -14,7 +12,6 @@ import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer;
|
|||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
|
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
|
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
|
||||||
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;
|
||||||
@@ -22,20 +19,16 @@ import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtensio
|
|||||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
|
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.PatherExtension;
|
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.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.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.ValidationIssues;
|
||||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
|
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
|
||||||
import lombok.Getter;
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.*;
|
||||||
import lombok.Value;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -48,7 +41,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
);
|
);
|
||||||
private static final ConfigEntryValidator PRIMITIVE_VALIDATOR = new ConfigEntryValidator() {
|
private static final ConfigEntryValidator PRIMITIVE_VALIDATOR = new ConfigEntryValidator() {
|
||||||
@Override
|
@Override
|
||||||
public <T> ValidationResult<T> validate(@NotNull ConfigEntry<T> configEntry, @Nullable T value) {
|
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (ValidationResult<T>) PRIMITIVE_IS_NULL_RESULT;
|
return (ValidationResult<T>) PRIMITIVE_IS_NULL_RESULT;
|
||||||
@@ -57,33 +50,32 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> String description(@NotNull ConfigEntry<T> configEntry) {
|
public <T> String description(ConfigEntry<T> configEntry) {
|
||||||
return "Value must not be null.";
|
return "Value must not be null.";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private static final ConfigEntryValidator NOOP_VALIDATOR = new ConfigEntryValidator() {
|
private static final ConfigEntryValidator NOOP_VALIDATOR = new ConfigEntryValidator() {
|
||||||
@Override
|
@Override
|
||||||
public <T> ValidationResult<T> validate(@NotNull ConfigEntry<T> configEntry, @Nullable T value) {
|
public <T> ValidationResult<@Nullable T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
||||||
return ValidationResult.ok(value);
|
return ValidationResult.ok(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> String description(@NotNull ConfigEntry<T> configEntry) {
|
public <T> String description(ConfigEntry<T> configEntry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final ConfigContainer<?> configContainer;
|
private final ConfigContainer<?> configContainer;
|
||||||
private final RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
|
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||||
private final MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer
|
private final MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer
|
||||||
= new DefaultMiddlewareContainer<>();
|
= new DefaultMiddlewareContainer<>();
|
||||||
private @Nullable RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues>
|
private @Nullable PatchworkPartAccess<ValidationIssues> readContextValidationIssuesAccess;
|
||||||
readContextValidationIssuesExtensionData;
|
private @Nullable PatherExtension patherExtension;
|
||||||
|
|
||||||
public ValidationExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
public ValidationExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
||||||
this.configContainer = configContainer;
|
this.configContainer = configContainer;
|
||||||
this.validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class);
|
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||||
context.registerEntryExtensionData(EntrySpecificValidation.class);
|
|
||||||
context.registerExtension(PatherExtension.class);
|
context.registerExtension(PatherExtension.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +94,9 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
entryValidatorMiddlewareContainer.seal();
|
entryValidatorMiddlewareContainer.seal();
|
||||||
|
|
||||||
|
patherExtension = configContainer.extension(PatherExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Missing requested PatherExtension"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -116,16 +111,21 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
public CommentProducer process(CommentProducer inner) {
|
public CommentProducer process(CommentProducer inner) {
|
||||||
return entry -> {
|
return entry -> {
|
||||||
String baseComment = inner.createComment(entry);
|
String baseComment = inner.createComment(entry);
|
||||||
if (entry.extensionsData().isPatchworkPartSet(InternalValidationEntryData.class)) {
|
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||||
String validationDescription = ((InternalValidationEntryData) entry.extensionsData())
|
if (entryData == null || entryData.completeValidator() == null) {
|
||||||
.completeEntryValidator()
|
return baseComment;
|
||||||
.description(entry)
|
}
|
||||||
.trim();
|
String validationDescription = entryData.completeValidator()
|
||||||
if (!validationDescription.isEmpty()) {
|
.description(entry)
|
||||||
baseComment += "\n\n" + validationDescription;
|
.trim();
|
||||||
}
|
if (validationDescription.isEmpty()) {
|
||||||
|
return baseComment;
|
||||||
|
}
|
||||||
|
if (baseComment.isEmpty()) {
|
||||||
|
return validationDescription;
|
||||||
|
} else {
|
||||||
|
return "\n\n" + validationDescription;
|
||||||
}
|
}
|
||||||
return baseComment;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -133,7 +133,13 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
||||||
readContextValidationIssuesExtensionData = context.registerReadWriteContextExtensionData(ValidationIssues.class);
|
readContextValidationIssuesAccess = context.registerReadWriteContextExtensionData(ValidationIssues.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void addValidatorMiddleware(ConfigEntry<T> entry, Middleware<ConfigEntryValidator> validator) {
|
||||||
|
CustomEntryData entryData = getOrCreateCustomEntryData(entry);
|
||||||
|
entryData.addValidator(validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,26 +151,26 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
baseValidator = NOOP_VALIDATOR;
|
baseValidator = NOOP_VALIDATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigEntryValidator entryValidator;
|
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
|
||||||
Collection<Middleware<ConfigEntryValidator>> entrySpecificValidators = getEntrySpecificValidators(configEntry);
|
|
||||||
if (entrySpecificValidators.isEmpty()) {
|
if (entryData.validators().isEmpty()) {
|
||||||
entryValidator = entryValidatorMiddlewareContainer.process(baseValidator);
|
entryData.completeValidator(entryValidatorMiddlewareContainer.process(baseValidator));
|
||||||
} else {
|
} else {
|
||||||
DefaultMiddlewareContainer<ConfigEntryValidator> entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>();
|
DefaultMiddlewareContainer<ConfigEntryValidator> entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>();
|
||||||
entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares());
|
entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares());
|
||||||
entrySpecificValidatorContainer.registerAll(entrySpecificValidators);
|
entrySpecificValidatorContainer.registerAll(entryData.validators());
|
||||||
entrySpecificValidatorContainer.seal();
|
entrySpecificValidatorContainer.seal();
|
||||||
entryValidator = entrySpecificValidatorContainer.process(baseValidator);
|
entryData.completeValidator(entrySpecificValidatorContainer.process(baseValidator));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationEntryDataExtension.set(configEntry.extensionsData(), new InternalValidationEntryDataImpl(entryValidator));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Middleware<ConfigEntryValidator>> getEntrySpecificValidators(ConfigEntry<?> configEntry) {
|
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
|
||||||
if (!configEntry.extensionsData().isPatchworkPartSet(EntrySpecificValidation.class)) {
|
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||||
return Collections.emptyList();
|
if (entryData == null) {
|
||||||
|
entryData = new CustomEntryData();
|
||||||
|
entry.extensionsData().set(customEntryDataAccess, entryData);
|
||||||
}
|
}
|
||||||
return ((EntrySpecificValidation) configEntry.extensionsData()).validators();
|
return entryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -173,7 +179,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> ValidationIssues validate(@NotNull ConfigEntry<T> entry, @Nullable T value) {
|
public <T> ValidationIssues validate(ConfigEntry<T> entry, @Nullable T value) {
|
||||||
PathTracking pathTracking = new PathTracking();
|
PathTracking pathTracking = new PathTracking();
|
||||||
ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking);
|
ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking);
|
||||||
|
|
||||||
@@ -182,9 +188,22 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
return validatingVisitor.validationIssues();
|
return validatingVisitor.validationIssues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Data
|
||||||
private static class InternalValidationEntryDataImpl implements InternalValidationEntryData {
|
private static class CustomEntryData {
|
||||||
ConfigEntryValidator completeEntryValidator;
|
@Setter(AccessLevel.NONE)
|
||||||
|
private @Nullable List<Middleware<ConfigEntryValidator>> validators;
|
||||||
|
private @Nullable ConfigEntryValidator completeValidator;
|
||||||
|
|
||||||
|
public List<Middleware<ConfigEntryValidator>> validators() {
|
||||||
|
return validators == null ? Collections.emptyList() : validators;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addValidator(Middleware<ConfigEntryValidator> validator) {
|
||||||
|
if (validators == null) {
|
||||||
|
validators = new ArrayList<>();
|
||||||
|
}
|
||||||
|
validators.add(validator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EntryValidationReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
|
private class EntryValidationReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
|
||||||
@@ -200,23 +219,29 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
||||||
|
assert readContextValidationIssuesAccess != null && patherExtension != null;
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
|
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
|
||||||
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
|
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
|
||||||
ValidationIssues validationIssues;
|
ValidationIssues validationIssues = context.extensionsData().get(readContextValidationIssuesAccess);
|
||||||
if (!context.extensionsData().isPatchworkPartSet(ValidationIssues.class)) {
|
if (validationIssues == null) {
|
||||||
validationIssues = new ValidationIssuesImpl();
|
validationIssues = new ValidationIssuesImpl();
|
||||||
readContextValidationIssuesExtensionData.set(context.extensionsData(), validationIssues);
|
|
||||||
} else {
|
} else {
|
||||||
validationIssues = (ValidationIssues) context.extensionsData();
|
validationIssues = (ValidationIssues) context.extensionsData();
|
||||||
}
|
}
|
||||||
|
|
||||||
Object value = castedInner.read(reader, entry, context);
|
Object value = castedInner.read(reader, entry, context);
|
||||||
|
|
||||||
ValidationResult<Object> validationResult = ((InternalValidationEntryData) entry.extensionsData()).completeEntryValidator().validate(entry, value);
|
ConfigEntryValidator entryValidator = entry.extensionsData()
|
||||||
|
.get(customEntryDataAccess)
|
||||||
|
.completeValidator();
|
||||||
|
assert entryValidator != null;
|
||||||
|
|
||||||
if (!validationResult.issues().isEmpty() && context.extensionsData().isPatchworkPartSet(PatherData.class)) {
|
ValidationResult<Object> validationResult = entryValidator.validate(entry, value);
|
||||||
String path = ((PatherData) context.extensionsData()).valuePath();
|
|
||||||
|
if (!validationResult.issues().isEmpty()) {
|
||||||
|
String path = patherExtension.getPath(context);
|
||||||
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
|
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
|
||||||
entry,
|
entry,
|
||||||
validationResult.issues()
|
validationResult.issues()
|
||||||
@@ -234,15 +259,19 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
private static class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor {
|
private class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor {
|
||||||
private final PathTracking pathTracking;
|
private final PathTracking pathTracking;
|
||||||
private final ValidationIssues validationIssues = new ValidationIssuesImpl();
|
private final ValidationIssues validationIssues = new ValidationIssuesImpl();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void visitEntry(ConfigEntry<T> entry, T value) {
|
public <T> void visitEntry(ConfigEntry<T> entry, T value) {
|
||||||
ValidationResult<T> result = ((InternalValidationEntryData) entry.extensionsData()).completeEntryValidator().validate(entry, value);
|
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||||
|
assert entryData != null;
|
||||||
|
ConfigEntryValidator entryValidator = entryData.completeValidator();
|
||||||
|
assert entryValidator != null;
|
||||||
|
ValidationResult<T> result = entryValidator.validate(entry, value);
|
||||||
if (!result.issues().isEmpty()) {
|
if (!result.issues().isEmpty()) {
|
||||||
validationIssues.issuesByPath().put(pathTracking.valuePath(), new ValidationIssues.EntryIssues(entry, result.issues()));
|
validationIssues.issuesByPath().put(pathTracking.currentPath(), new ValidationIssues.EntryIssues(entry, result.issues()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.validationfallback.api;
|
package de.siphalor.tweed5.defaultextensions.validationfallback.api;
|
||||||
|
|
||||||
|
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.validationfallback.impl.ValidationFallbackExtensionImpl;
|
import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface ValidationFallbackExtension extends TweedExtension {
|
public interface ValidationFallbackExtension extends TweedExtension {
|
||||||
Class<? extends ValidationFallbackExtension> DEFAULT = ValidationFallbackExtensionImpl.class;
|
Class<? extends ValidationFallbackExtension> DEFAULT = ValidationFallbackExtensionImpl.class;
|
||||||
|
|
||||||
|
static <C extends ConfigEntry<T>, T> Consumer<C> validationFallbackValue(T value) {
|
||||||
|
return entry -> {
|
||||||
|
ValidationFallbackExtension extension = entry.container().extension(ValidationFallbackExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("ValidationFallbackExtension is not registered"));
|
||||||
|
extension.setFallbackValue(entry, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> void setFallbackValue(ConfigEntry<T> entry, T value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.validationfallback.api;
|
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
public interface ValidationFallbackValue {
|
|
||||||
@Nullable Object validationFallbackValue();
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,8 @@ 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.validationfallback.api.ValidationFallbackExtension;
|
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
|
||||||
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue;
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
import lombok.Data;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -20,8 +21,11 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
@AutoService(ValidationFallbackExtension.class)
|
@AutoService(ValidationFallbackExtension.class)
|
||||||
public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension {
|
public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension {
|
||||||
|
|
||||||
|
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||||
|
|
||||||
public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) {
|
public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) {
|
||||||
context.registerEntryExtensionData(ValidationFallbackValue.class);
|
customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,12 +33,31 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
|
|||||||
return "validation-fallback";
|
return "validation-fallback";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void setFallbackValue(ConfigEntry<T> entry, T value) {
|
||||||
|
getOrCreateCustomEntryData(entry).fallbackValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
|
||||||
|
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
|
||||||
|
if (customEntryData == null) {
|
||||||
|
customEntryData = new CustomEntryData();
|
||||||
|
entry.extensionsData().set(customEntryDataAccess, customEntryData);
|
||||||
|
}
|
||||||
|
return customEntryData;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Middleware<ConfigEntryValidator> validationMiddleware() {
|
public Middleware<ConfigEntryValidator> validationMiddleware() {
|
||||||
return new ValidationFallbackMiddleware();
|
return new ValidationFallbackMiddleware();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator> {
|
@Data
|
||||||
|
private static class CustomEntryData {
|
||||||
|
@Nullable Object fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator> {
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "validation-fallback";
|
return "validation-fallback";
|
||||||
@@ -59,13 +82,14 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
|
|||||||
if (!result.hasError()) {
|
if (!result.hasError()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) {
|
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
|
||||||
|
if (entryData == null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object fallbackValue = ((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue();
|
Object fallbackValue = entryData.fallbackValue();
|
||||||
if (fallbackValue != null) {
|
if (fallbackValue != null) {
|
||||||
if (fallbackValue.getClass() == configEntry.valueClass()) {
|
if (configEntry.valueClass().isInstance(fallbackValue)) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
fallbackValue = configEntry.deepCopy((T) fallbackValue);
|
fallbackValue = configEntry.deepCopy((T) fallbackValue);
|
||||||
} else {
|
} else {
|
||||||
@@ -90,12 +114,11 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> String description(ConfigEntry<T> configEntry) {
|
public <T> String description(ConfigEntry<T> configEntry) {
|
||||||
if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) {
|
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
|
||||||
|
if (entryData == null) {
|
||||||
return inner.description(configEntry);
|
return inner.description(configEntry);
|
||||||
}
|
}
|
||||||
return inner.description(configEntry) +
|
return inner.description(configEntry) + "\n\nDefault/Fallback value: " + entryData.fallbackValue();
|
||||||
"\n\nDefault/Fallback value: " +
|
|
||||||
((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,20 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.comment.impl;
|
package de.siphalor.tweed5.defaultextensions.comment.impl;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
|
|
||||||
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 lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.jspecify.annotations.NonNull;
|
||||||
import lombok.Value;
|
|
||||||
import org.jspecify.annotations.NullUnmarked;
|
import org.jspecify.annotations.NullUnmarked;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -31,6 +24,9 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
||||||
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
|
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
@@ -38,44 +34,44 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
@NullUnmarked
|
@NullUnmarked
|
||||||
class CommentExtensionImplTest {
|
class CommentExtensionImplTest {
|
||||||
|
|
||||||
private DefaultConfigContainer<Map<String, Object>> configContainer;
|
private DefaultConfigContainer<@NonNull Map<String, Object>> configContainer;
|
||||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
private SimpleConfigEntry<Integer> intEntry;
|
||||||
private SimpleConfigEntryImpl<String> stringEntry;
|
private SimpleConfigEntry<String> stringEntry;
|
||||||
private SimpleConfigEntryImpl<Long> noCommentEntry;
|
private SimpleConfigEntry<Long> noCommentEntry;
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
final void setupContainer(Class<? extends TweedExtension>... extraExtensions) {
|
final void setupContainer(Class<? extends TweedExtension>... extraExtensions) {
|
||||||
configContainer = new DefaultConfigContainer<>();
|
configContainer = new DefaultConfigContainer<>();
|
||||||
|
|
||||||
configContainer.registerExtension(CommentExtension.DEFAULT);
|
configContainer.registerExtension(CommentExtension.DEFAULT);
|
||||||
|
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
|
||||||
configContainer.registerExtensions(extraExtensions);
|
configContainer.registerExtensions(extraExtensions);
|
||||||
configContainer.finishExtensionSetup();
|
configContainer.finishExtensionSetup();
|
||||||
|
|
||||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class);
|
.apply(entryReaderWriter(intReaderWriter()))
|
||||||
noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class);
|
.apply(baseComment("It is an integer"));
|
||||||
|
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
|
||||||
|
.apply(entryReaderWriter(stringReaderWriter()))
|
||||||
|
.apply(baseComment("It is a string"));
|
||||||
|
noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class)
|
||||||
|
.apply(entryReaderWriter(longReaderWriter()));
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||||
configContainer,
|
configContainer,
|
||||||
((Class<Map<String, Object>>)(Class<?>) Map.class),
|
((Class<Map<String, Object>>) (Class<?>) Map.class),
|
||||||
LinkedHashMap::new,
|
LinkedHashMap::new,
|
||||||
sequencedMap(List.of(
|
sequencedMap(List.of(
|
||||||
entry("int", intEntry),
|
entry("int", intEntry),
|
||||||
entry("string", stringEntry),
|
entry("string", stringEntry),
|
||||||
entry("noComment", noCommentEntry)
|
entry("noComment", noCommentEntry)
|
||||||
))
|
)))
|
||||||
);
|
.apply(entryReaderWriter(compoundReaderWriter()))
|
||||||
|
.apply(baseComment("This is the root value.\nIt is the topmost value in the tree."));
|
||||||
|
|
||||||
configContainer.attachTree(rootEntry);
|
configContainer.attachTree(rootEntry);
|
||||||
|
|
||||||
//noinspection unchecked
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class);
|
|
||||||
|
|
||||||
commentData.set(rootEntry.extensionsData(), new CommentImpl("This is the root value.\nIt is the topmost value in the tree."));
|
|
||||||
commentData.set(intEntry.extensionsData(), new CommentImpl("It is an integer"));
|
|
||||||
commentData.set(stringEntry.extensionsData(), new CommentImpl("It is a string"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -102,8 +98,7 @@ class CommentExtensionImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void simpleCommentsInHjson() {
|
void simpleCommentsInHjson() {
|
||||||
setupContainer(ReadWriteExtension.DEFAULT);
|
setupContainer();
|
||||||
setupReadWriteTypes();
|
|
||||||
configContainer.initialize();
|
configContainer.initialize();
|
||||||
|
|
||||||
Map<String, Object> value = new HashMap<>();
|
Map<String, Object> value = new HashMap<>();
|
||||||
@@ -134,21 +129,6 @@ class CommentExtensionImplTest {
|
|||||||
""", output.toString());
|
""", output.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupReadWriteTypes() {
|
|
||||||
//noinspection unchecked
|
|
||||||
var readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
|
|
||||||
|
|
||||||
readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter()));
|
|
||||||
readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter()));
|
|
||||||
readerWriterData.set(stringEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.stringReaderWriter()));
|
|
||||||
readerWriterData.set(noCommentEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.longReaderWriter()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
private static class CommentImpl implements EntryComment {
|
|
||||||
String comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension {
|
public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension {
|
||||||
@Override
|
@Override
|
||||||
@@ -158,7 +138,7 @@ class CommentExtensionImplTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Middleware<CommentProducer> commentMiddleware() {
|
public Middleware<CommentProducer> commentMiddleware() {
|
||||||
return new Middleware<CommentProducer>() {
|
return new Middleware<>() {
|
||||||
@Override
|
@Override
|
||||||
public String id() {
|
public String id() {
|
||||||
return "test";
|
return "test";
|
||||||
@@ -171,19 +151,4 @@ class CommentExtensionImplTest {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
private static class TrivialEntryReaderWriterDefinition implements EntryReaderWriterDefinition {
|
|
||||||
private final TweedEntryReaderWriter<?, ?> readerWriter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TweedEntryReader<?, ?> reader() {
|
|
||||||
return readerWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TweedEntryWriter<?, ?> writer() {
|
|
||||||
return readerWriter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,39 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.validation.impl;
|
package de.siphalor.tweed5.defaultextensions.validation.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.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.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;
|
||||||
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.ValidationIssues;
|
||||||
import de.siphalor.tweed5.defaultextensions.validation.api.validators.NumberRangeValidator;
|
import de.siphalor.tweed5.defaultextensions.validation.api.validators.NumberRangeValidator;
|
||||||
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
|
|
||||||
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;
|
||||||
import org.junit.jupiter.params.provider.CsvSource;
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
|
||||||
|
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class ValidationExtensionImplTest {
|
class ValidationExtensionImplTest {
|
||||||
private DefaultConfigContainer<Map<String, Object>> configContainer;
|
private DefaultConfigContainer<Map<String, Object>> configContainer;
|
||||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||||
private SimpleConfigEntryImpl<Byte> byteEntry;
|
private SimpleConfigEntry<Byte> byteEntry;
|
||||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
private SimpleConfigEntry<Integer> intEntry;
|
||||||
private SimpleConfigEntryImpl<Double> doubleEntry;
|
private SimpleConfigEntry<Double> doubleEntry;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
@@ -41,9 +43,13 @@ class ValidationExtensionImplTest {
|
|||||||
configContainer.registerExtension(ValidationExtension.DEFAULT);
|
configContainer.registerExtension(ValidationExtension.DEFAULT);
|
||||||
configContainer.finishExtensionSetup();
|
configContainer.finishExtensionSetup();
|
||||||
|
|
||||||
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class);
|
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class)
|
||||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
.apply(validators(new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100)));
|
||||||
doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class);
|
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
|
.apply(validators(new NumberRangeValidator<>(Integer.class, null, 123)))
|
||||||
|
.apply(baseComment("This is the main comment!"));
|
||||||
|
doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class)
|
||||||
|
.apply(validators(new NumberRangeValidator<>(Double.class, 0.5, null)));
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||||
@@ -59,25 +65,6 @@ class ValidationExtensionImplTest {
|
|||||||
|
|
||||||
|
|
||||||
configContainer.attachTree(rootEntry);
|
configContainer.attachTree(rootEntry);
|
||||||
|
|
||||||
//noinspection unchecked
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class);
|
|
||||||
commentData.set(intEntry.extensionsData(), () -> "This is the main comment!");
|
|
||||||
//noinspection unchecked
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class);
|
|
||||||
entrySpecificValidation.set(
|
|
||||||
byteEntry.extensionsData(),
|
|
||||||
() -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100)))
|
|
||||||
);
|
|
||||||
entrySpecificValidation.set(
|
|
||||||
intEntry.extensionsData(),
|
|
||||||
() -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Integer.class, null, 123)))
|
|
||||||
);
|
|
||||||
entrySpecificValidation.set(
|
|
||||||
doubleEntry.extensionsData(),
|
|
||||||
() -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Double.class, 0.5, null)))
|
|
||||||
);
|
|
||||||
|
|
||||||
configContainer.initialize();
|
configContainer.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
package de.siphalor.tweed5.defaultextensions.validationfallback.impl;
|
package de.siphalor.tweed5.defaultextensions.validationfallback.impl;
|
||||||
|
|
||||||
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.entry.SimpleConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
|
|
||||||
import de.siphalor.tweed5.data.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.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.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;
|
||||||
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.validationfallback.api.ValidationFallbackExtension;
|
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
|
||||||
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.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;
|
||||||
@@ -33,18 +24,19 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*;
|
||||||
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
|
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
|
||||||
|
import static de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension.validationFallbackValue;
|
||||||
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 ValidationFallbackExtensionImplTest {
|
class ValidationFallbackExtensionImplTest {
|
||||||
private DefaultConfigContainer<Integer> configContainer;
|
private DefaultConfigContainer<Integer> configContainer;
|
||||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
private SimpleConfigEntry<Integer> intEntry;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
configContainer = new DefaultConfigContainer<>();
|
configContainer = new DefaultConfigContainer<>();
|
||||||
@@ -55,92 +47,82 @@ class ValidationFallbackExtensionImplTest {
|
|||||||
|
|
||||||
configContainer.finishExtensionSetup();
|
configContainer.finishExtensionSetup();
|
||||||
|
|
||||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
|
.apply(entryReaderWriter(nullableReader(intReaderWriter()), nullableWriter(intReaderWriter())))
|
||||||
|
.apply(validators(
|
||||||
|
new ConfigEntryValidator() {
|
||||||
|
@Override
|
||||||
|
public <T extends @Nullable Object> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
||||||
|
if (value == null) {
|
||||||
|
return ValidationResult.withIssues(
|
||||||
|
null, Collections.singleton(
|
||||||
|
new ValidationIssue(
|
||||||
|
"Value must not be null",
|
||||||
|
ValidationIssueLevel.ERROR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ValidationResult.ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> String description(ConfigEntry<T> configEntry) {
|
||||||
|
return "Must not be null.";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ConfigEntryValidator() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
||||||
|
assert value != null;
|
||||||
|
int intValue = (int) value;
|
||||||
|
if (intValue < 1) {
|
||||||
|
return ValidationResult.withIssues(
|
||||||
|
value,
|
||||||
|
Collections.singleton(new ValidationIssue(
|
||||||
|
"Must be greater or equal to 1",
|
||||||
|
ValidationIssueLevel.ERROR
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (intValue > 6) {
|
||||||
|
return ValidationResult.withIssues(
|
||||||
|
value,
|
||||||
|
Collections.singleton(new ValidationIssue(
|
||||||
|
"Must be smaller or equal to 6",
|
||||||
|
ValidationIssueLevel.ERROR
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ValidationResult.ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> String description(ConfigEntry<T> configEntry) {
|
||||||
|
return "Must be between 1 and 6";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.apply(validationFallbackValue(3));
|
||||||
|
|
||||||
configContainer.attachTree(intEntry);
|
configContainer.attachTree(intEntry);
|
||||||
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class);
|
|
||||||
entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList(
|
|
||||||
new SimpleValidatorMiddleware("non-null", new ConfigEntryValidator() {
|
|
||||||
@Override
|
|
||||||
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
|
||||||
if (value == null) {
|
|
||||||
return ValidationResult.withIssues(null, Collections.singleton(
|
|
||||||
new ValidationIssue("Value must not be null", ValidationIssueLevel.ERROR)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return ValidationResult.ok(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
|
|
||||||
return "Must not be null.";
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new SimpleValidatorMiddleware("range", new ConfigEntryValidator() {
|
|
||||||
@Override
|
|
||||||
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
|
||||||
Integer intValue = (Integer) value;
|
|
||||||
if (intValue < 1) {
|
|
||||||
return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be greater or equal to 1", ValidationIssueLevel.ERROR)));
|
|
||||||
}
|
|
||||||
if (intValue > 6) {
|
|
||||||
return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be smaller or equal to 6", ValidationIssueLevel.ERROR)));
|
|
||||||
}
|
|
||||||
return ValidationResult.ok(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
|
|
||||||
return "Must be between 1 and 6";
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
@Override
|
|
||||||
public Set<String> mustComeAfter() {
|
|
||||||
return Collections.singleton("non-null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, ValidationFallbackValue> validationFallbackValue = (RegisteredExtensionData<EntryExtensionsData, ValidationFallbackValue>) configContainer.entryDataExtensions().get(ValidationFallbackValue.class);
|
|
||||||
validationFallbackValue.set(intEntry.extensionsData(), () -> 3);
|
|
||||||
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
|
|
||||||
readerWriterData.set(intEntry.extensionsData(), new EntryReaderWriterDefinition() {
|
|
||||||
@Override
|
|
||||||
public TweedEntryReader<?, ?> reader() {
|
|
||||||
return TweedEntryReaderWriters.nullableReader(TweedEntryReaderWriters.intReaderWriter());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TweedEntryWriter<?, ?> writer() {
|
|
||||||
return TweedEntryReaderWriters.nullableWriter(TweedEntryReaderWriters.intReaderWriter());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
configContainer.initialize();
|
configContainer.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input)))));
|
||||||
Integer result = assertDoesNotThrow(() -> readWriteExtension.read(
|
|
||||||
new HjsonReader(new HjsonLexer(new StringReader(input))),
|
|
||||||
intEntry,
|
|
||||||
readWriteExtension.createReadWriteContextExtensionsData()
|
|
||||||
));
|
|
||||||
assertEquals(3, result);
|
assertEquals(3, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void description() {
|
void description() {
|
||||||
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
StringWriter stringWriter = new StringWriter();
|
||||||
assertDoesNotThrow(() -> readWriteExtension.write(
|
intEntry.apply(write(
|
||||||
new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)),
|
new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)),
|
||||||
5,
|
5
|
||||||
intEntry,
|
|
||||||
readWriteExtension.createReadWriteContextExtensionsData()
|
|
||||||
));
|
));
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("de.siphalor.tweed5.base-module")
|
id("de.siphalor.tweed5.base-module")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.asm.core)
|
|
||||||
implementation(libs.asm.commons)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.siphalor.tweed5.patchwork.api;
|
||||||
|
|
||||||
|
public class InvalidPatchworkAccessException extends RuntimeException {
|
||||||
|
public InvalidPatchworkAccessException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
package de.siphalor.tweed5.patchwork.api;
|
package de.siphalor.tweed5.patchwork.api;
|
||||||
|
|
||||||
public interface Patchwork<S extends Patchwork<S>> {
|
import org.jetbrains.annotations.CheckReturnValue;
|
||||||
boolean isPatchworkPartDefined(Class<?> patchworkInterface);
|
import org.jetbrains.annotations.Contract;
|
||||||
boolean isPatchworkPartSet(Class<?> patchworkInterface);
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
S copy();
|
public interface Patchwork {
|
||||||
|
@Contract(pure = true)
|
||||||
|
<T extends @Nullable Object> @Nullable T get(PatchworkPartAccess<T> access) throws InvalidPatchworkAccessException;
|
||||||
|
@Contract(mutates = "this")
|
||||||
|
<T extends @Nullable Object> void set(PatchworkPartAccess<T> access, T value) throws InvalidPatchworkAccessException;
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
@Contract(pure = true)
|
||||||
|
Patchwork copy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.api;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.patchwork.impl.ByteArrayClassLoader;
|
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
public class PatchworkClassCreator<P extends Patchwork<P>> {
|
|
||||||
Class<P> patchworkInterface;
|
|
||||||
PatchworkClassGenerator.Config generatorConfig;
|
|
||||||
|
|
||||||
public static <P extends Patchwork<P>> Builder<P> builder() {
|
|
||||||
return new Builder<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatchworkClass<P> createClass(Collection<Class<?>> partInterfaces) throws
|
|
||||||
PatchworkClassGenerator.GenerationException {
|
|
||||||
List<PatchworkClassPart> parts = partInterfaces.stream()
|
|
||||||
.map(PatchworkClassPart::new)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
PatchworkClassGenerator generator = new PatchworkClassGenerator(generatorConfig, parts);
|
|
||||||
try {
|
|
||||||
generator.verify();
|
|
||||||
} catch (PatchworkClassGenerator.VerificationException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
generator.generate();
|
|
||||||
byte[] classBytes = generator.emit();
|
|
||||||
//noinspection unchecked
|
|
||||||
Class<P> patchworkClass = (Class<P>) ByteArrayClassLoader.loadClass(null, classBytes);
|
|
||||||
|
|
||||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
try {
|
|
||||||
MethodHandle setterHandle = lookup.findSetter(patchworkClass, part.fieldName(), part.partInterface());
|
|
||||||
part.fieldSetter(setterHandle);
|
|
||||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Failed to access setter for patchwork part " + part.partInterface().getName(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
MethodHandle constructorHandle = lookup.findConstructor(patchworkClass, MethodType.methodType(Void.TYPE));
|
|
||||||
|
|
||||||
return PatchworkClass.<P>builder()
|
|
||||||
.classPackage(generatorConfig.classPackage())
|
|
||||||
.className(generator.className())
|
|
||||||
.theClass(patchworkClass)
|
|
||||||
.constructor(constructorHandle)
|
|
||||||
.parts(parts)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
|
||||||
throw new IllegalStateException("Failed to access constructor of patchwork class", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public static class Builder<P extends Patchwork<P>> {
|
|
||||||
private Class<P> patchworkInterface;
|
|
||||||
private String classPackage;
|
|
||||||
private String classPrefix = "";
|
|
||||||
|
|
||||||
public PatchworkClassCreator<P> build() {
|
|
||||||
return new PatchworkClassCreator<>(
|
|
||||||
patchworkInterface,
|
|
||||||
new PatchworkClassGenerator.Config(classPackage)
|
|
||||||
.classPrefix(classPrefix)
|
|
||||||
.markerInterfaces(Collections.singletonList(patchworkInterface))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.siphalor.tweed5.patchwork.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.patchwork.impl.PatchworkFactoryImpl;
|
||||||
|
|
||||||
|
public interface PatchworkFactory {
|
||||||
|
static Builder builder() {
|
||||||
|
return new PatchworkFactoryImpl.Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
Patchwork create();
|
||||||
|
|
||||||
|
interface Builder {
|
||||||
|
<T> PatchworkPartAccess<T> registerPart(Class<T> partClass);
|
||||||
|
PatchworkFactory build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package de.siphalor.tweed5.patchwork.api;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface PatchworkPartAccess<T extends @Nullable Object> {
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.api;
|
|
||||||
|
|
||||||
public class PatchworkPartIsNullException extends RuntimeException {
|
|
||||||
public PatchworkPartIsNullException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl;
|
|
||||||
|
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
public class ByteArrayClassLoader extends ClassLoader {
|
|
||||||
public static Class<?> loadClass(@Nullable String binaryClassName, byte[] byteCode) {
|
|
||||||
return new ByteArrayClassLoader(ByteArrayClassLoader.class.getClassLoader())
|
|
||||||
.createClass(binaryClassName, byteCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ByteArrayClassLoader(ClassLoader parent) {
|
|
||||||
super(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> createClass(@Nullable String binaryClassName, byte[] byteCode) {
|
|
||||||
Class<?> clazz = defineClass(binaryClassName, byteCode, 0, byteCode.length);
|
|
||||||
resolveClass(clazz);
|
|
||||||
return clazz;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@Value
|
|
||||||
@Builder
|
|
||||||
public class PatchworkClass<P extends Patchwork<P>> {
|
|
||||||
String classPackage;
|
|
||||||
String className;
|
|
||||||
Class<P> theClass;
|
|
||||||
MethodHandle constructor;
|
|
||||||
Collection<PatchworkClassPart> parts;
|
|
||||||
}
|
|
||||||
@@ -1,653 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
|
||||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartIsNullException;
|
|
||||||
import de.siphalor.tweed5.patchwork.impl.util.StreamUtils;
|
|
||||||
import lombok.*;
|
|
||||||
import org.jspecify.annotations.NonNull;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
import org.objectweb.asm.*;
|
|
||||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class PatchworkClassGenerator {
|
|
||||||
/**
|
|
||||||
* Class version to use (Java 8)
|
|
||||||
*/
|
|
||||||
private static final int CLASS_VERSION = Opcodes.V1_8;
|
|
||||||
|
|
||||||
private static final String INNER_EQUALS_METHOD_NAME = "patchwork$innerEquals";
|
|
||||||
|
|
||||||
private static String generateUniqueIdentifier() {
|
|
||||||
UUID uuid = UUID.randomUUID();
|
|
||||||
return uuid.toString().replace("-", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
private final Collection<PatchworkClassPart> parts;
|
|
||||||
private final String className;
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
private final ClassWriter classWriter;
|
|
||||||
|
|
||||||
public PatchworkClassGenerator(Config config, Collection<PatchworkClassPart> parts) {
|
|
||||||
this.config = config;
|
|
||||||
this.parts = parts;
|
|
||||||
className = config.classPrefix() + generateUniqueIdentifier();
|
|
||||||
classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String internalClassName() {
|
|
||||||
return config.classPackage().replace('.', '/') + "/" + className();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String binaryClassName() {
|
|
||||||
return config.classPackage() + "." + className();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void verify() throws VerificationException {
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
verifyClass(part.partInterface());
|
|
||||||
}
|
|
||||||
verifyPartMethods();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyClass(Class<?> partClass) throws InvalidPatchworkPartClassException {
|
|
||||||
if (!partClass.isInterface()) {
|
|
||||||
throw new InvalidPatchworkPartClassException(partClass, "Must be an interface");
|
|
||||||
}
|
|
||||||
if ((partClass.getModifiers() & Modifier.PUBLIC) == 0) {
|
|
||||||
throw new InvalidPatchworkPartClassException(partClass, "Interface must be public");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyPartMethods() throws DuplicateMethodsException {
|
|
||||||
Map<MethodDescriptor, Collection<Method>> methodsBySignature = new HashMap<>();
|
|
||||||
|
|
||||||
for (PatchworkClassPart patchworkPart : parts) {
|
|
||||||
for (Method method : patchworkPart.partInterface().getMethods()) {
|
|
||||||
MethodDescriptor signature = new MethodDescriptor(method.getName(), method.getParameterTypes());
|
|
||||||
|
|
||||||
methodsBySignature
|
|
||||||
.computeIfAbsent(signature, s -> new ArrayList<>())
|
|
||||||
.add(method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Method> duplicateMethods = methodsBySignature.entrySet().stream()
|
|
||||||
.filter(entry -> entry.getValue().size() > 1)
|
|
||||||
.flatMap(entry -> entry.getValue().stream())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (!duplicateMethods.isEmpty()) {
|
|
||||||
throw new DuplicateMethodsException(duplicateMethods);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generate() throws GenerationException {
|
|
||||||
beginClass();
|
|
||||||
generateSimpleConstructor();
|
|
||||||
for (PatchworkClassPart extensionClass : parts) {
|
|
||||||
addPart(extensionClass);
|
|
||||||
}
|
|
||||||
appendPojoMethods();
|
|
||||||
appendDefaultPatchworkMethods();
|
|
||||||
classWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] emit() {
|
|
||||||
return classWriter.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beginClass() {
|
|
||||||
String[] interfaces = StreamUtils.concat(
|
|
||||||
Stream.of(Type.getInternalName(Patchwork.class)),
|
|
||||||
config.markerInterfaces().stream().map(Type::getInternalName),
|
|
||||||
parts.stream().map(ext -> Type.getInternalName(ext.partInterface()))
|
|
||||||
).toArray(String[]::new);
|
|
||||||
|
|
||||||
classWriter.visit(
|
|
||||||
CLASS_VERSION,
|
|
||||||
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL,
|
|
||||||
internalClassName(),
|
|
||||||
null,
|
|
||||||
Type.getInternalName(Object.class),
|
|
||||||
interfaces
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateSimpleConstructor() {
|
|
||||||
MethodVisitor methodWriter = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
|
|
||||||
methodWriter.visitCode();
|
|
||||||
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
|
||||||
methodWriter.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
|
||||||
methodWriter.visitInsn(Opcodes.RETURN);
|
|
||||||
methodWriter.visitMaxs(1, 1);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendPojoMethods() {
|
|
||||||
appendEqualsMethod();
|
|
||||||
appendHashCodeMethod();
|
|
||||||
appendToStringMethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
// <editor-fold desc="POJO Methods">
|
|
||||||
private void appendEqualsMethod() {
|
|
||||||
appendInnerEqualsMethod();
|
|
||||||
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
"equals",
|
|
||||||
"(Ljava/lang/Object;)Z",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
methodWriter.visitParameter("other", Opcodes.ACC_FINAL);
|
|
||||||
methodWriter.visitCode();
|
|
||||||
|
|
||||||
|
|
||||||
Label falseLabel = methodWriter.newLabel();
|
|
||||||
Label continueLabel = methodWriter.newLabel();
|
|
||||||
|
|
||||||
methodWriter.loadArg(0);
|
|
||||||
methodWriter.loadThis();
|
|
||||||
methodWriter.visitJumpInsn(Opcodes.IF_ACMPNE, continueLabel);
|
|
||||||
methodWriter.push(true);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitLabel(continueLabel);
|
|
||||||
methodWriter.loadArg(0);
|
|
||||||
methodWriter.visitTypeInsn(Opcodes.INSTANCEOF, internalClassName());
|
|
||||||
methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel);
|
|
||||||
|
|
||||||
methodWriter.loadArg(0);
|
|
||||||
methodWriter.visitTypeInsn(Opcodes.CHECKCAST, internalClassName());
|
|
||||||
methodWriter.loadThis();
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKESPECIAL,
|
|
||||||
internalClassName(),
|
|
||||||
INNER_EQUALS_METHOD_NAME,
|
|
||||||
"(L" + internalClassName() + ";)Z",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel);
|
|
||||||
|
|
||||||
methodWriter.push(true);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitLabel(falseLabel);
|
|
||||||
methodWriter.push(false);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitMaxs(0, 0);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendInnerEqualsMethod() {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PRIVATE,
|
|
||||||
INNER_EQUALS_METHOD_NAME,
|
|
||||||
"(L" + internalClassName() + ";)Z",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
methodWriter.visitParameter("other", Opcodes.ACC_FINAL);
|
|
||||||
methodWriter.visitCode();
|
|
||||||
|
|
||||||
Label falseLabel = methodWriter.newLabel();
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
methodWriter.loadArg(0);
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.GETFIELD);
|
|
||||||
methodWriter.loadThis();
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.GETFIELD);
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKESTATIC,
|
|
||||||
Type.getInternalName(Objects.class),
|
|
||||||
"equals",
|
|
||||||
"(Ljava/lang/Object;Ljava/lang/Object;)Z",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
methodWriter.push(true);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitLabel(falseLabel);
|
|
||||||
methodWriter.push(false);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitMaxs(0, 0);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendHashCodeMethod() {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
"hashCode",
|
|
||||||
"()I",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
methodWriter.visitCode();
|
|
||||||
|
|
||||||
methodWriter.push(parts.size());
|
|
||||||
methodWriter.newArray(Type.getType(Object.class));
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
methodWriter.dup();
|
|
||||||
methodWriter.push(i);
|
|
||||||
|
|
||||||
methodWriter.loadThis();
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.GETFIELD);
|
|
||||||
|
|
||||||
methodWriter.visitInsn(Opcodes.AASTORE);
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKESTATIC,
|
|
||||||
"java/util/Objects",
|
|
||||||
"hash",
|
|
||||||
"([Ljava/lang/Object;)I",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitMaxs(0, 0);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendToStringMethod() {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
"toString",
|
|
||||||
"()Ljava/lang/String;",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
methodWriter.visitCode();
|
|
||||||
|
|
||||||
String stringBuilderType = Type.getInternalName(StringBuilder.class);
|
|
||||||
methodWriter.visitTypeInsn(Opcodes.NEW, stringBuilderType);
|
|
||||||
methodWriter.dup();
|
|
||||||
methodWriter.push(className().length() + 10 + parts.size() * 64);
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKESPECIAL,
|
|
||||||
stringBuilderType,
|
|
||||||
"<init>",
|
|
||||||
"(I)V",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
StringBuilder constantConcat = new StringBuilder();
|
|
||||||
constantConcat.append(className()).append("{\n");
|
|
||||||
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
constantConcat.append("\t").append(part.partInterface().getSimpleName()).append(": ");
|
|
||||||
methodWriter.push(constantConcat.toString());
|
|
||||||
constantConcat.setLength(0);
|
|
||||||
visitStringBuilderAppendString(methodWriter);
|
|
||||||
|
|
||||||
Label nullLabel = methodWriter.newLabel();
|
|
||||||
Label continueLabel = methodWriter.newLabel();
|
|
||||||
methodWriter.loadThis();
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.GETFIELD);
|
|
||||||
methodWriter.dup();
|
|
||||||
methodWriter.visitJumpInsn(Opcodes.IFNULL, nullLabel);
|
|
||||||
|
|
||||||
visitToString(methodWriter);
|
|
||||||
methodWriter.visitJumpInsn(Opcodes.GOTO, continueLabel);
|
|
||||||
|
|
||||||
methodWriter.visitLabel(nullLabel);
|
|
||||||
methodWriter.pop();
|
|
||||||
methodWriter.push("<unset>");
|
|
||||||
|
|
||||||
methodWriter.visitLabel(continueLabel);
|
|
||||||
visitStringBuilderAppendString(methodWriter);
|
|
||||||
|
|
||||||
constantConcat.append(",\n");
|
|
||||||
}
|
|
||||||
constantConcat.append("}");
|
|
||||||
methodWriter.push(constantConcat.toString());
|
|
||||||
visitStringBuilderAppendString(methodWriter);
|
|
||||||
visitToString(methodWriter);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
methodWriter.visitMaxs(0, 0);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
// </editor-fold>
|
|
||||||
|
|
||||||
private void appendDefaultPatchworkMethods() {
|
|
||||||
appendCopyMethod();
|
|
||||||
appendIsPatchworkPartDefinedMethod();
|
|
||||||
appendIsPatchworkPartSetMethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
// <editor-fold desc="Patchwork Methods">
|
|
||||||
private void appendCopyMethod() {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
"copy",
|
|
||||||
"()L" + Type.getInternalName(Patchwork.class) + ";",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
methodWriter.visitCode();
|
|
||||||
methodWriter.visitTypeInsn(Opcodes.NEW, internalClassName());
|
|
||||||
methodWriter.dup();
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKESPECIAL,
|
|
||||||
internalClassName(),
|
|
||||||
"<init>",
|
|
||||||
"()V",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
methodWriter.dup();
|
|
||||||
methodWriter.loadThis();
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.GETFIELD);
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.PUTFIELD);
|
|
||||||
}
|
|
||||||
methodWriter.returnValue();
|
|
||||||
methodWriter.visitMaxs(0, 0);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendIsPatchworkPartDefinedMethod() {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
"isPatchworkPartDefined",
|
|
||||||
"(Ljava/lang/Class;)Z",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
methodWriter.visitParameter(null, Opcodes.ACC_FINAL);
|
|
||||||
methodWriter.visitCode();
|
|
||||||
Label trueLabel = methodWriter.newLabel();
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
methodWriter.loadArg(0);
|
|
||||||
methodWriter.push(Type.getType(part.partInterface()));
|
|
||||||
methodWriter.ifCmp(Type.getType(Object.class), GeneratorAdapter.EQ, trueLabel);
|
|
||||||
}
|
|
||||||
methodWriter.push(false);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
methodWriter.visitLabel(trueLabel);
|
|
||||||
methodWriter.push(true);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
methodWriter.visitMaxs(0, 0);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendIsPatchworkPartSetMethod() {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
"isPatchworkPartSet",
|
|
||||||
"(Ljava/lang/Class;)Z",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
methodWriter.visitParameter(null, Opcodes.ACC_FINAL);
|
|
||||||
methodWriter.visitCode();
|
|
||||||
Label[] labels = new Label[parts.size()];
|
|
||||||
int i = 0;
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
labels[i] = methodWriter.newLabel();
|
|
||||||
methodWriter.loadArg(0);
|
|
||||||
methodWriter.push(Type.getType(part.partInterface()));
|
|
||||||
methodWriter.ifCmp(Type.getType(Object.class), GeneratorAdapter.EQ, labels[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
methodWriter.push(false);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
|
|
||||||
Label falseLabel = methodWriter.newLabel();
|
|
||||||
i = 0;
|
|
||||||
for (PatchworkClassPart part : parts) {
|
|
||||||
methodWriter.visitLabel(labels[i]);
|
|
||||||
methodWriter.loadThis();
|
|
||||||
visitFieldInsn(methodWriter, part, Opcodes.GETFIELD);
|
|
||||||
methodWriter.push((String) null);
|
|
||||||
methodWriter.ifCmp(Type.getType(part.partInterface()), GeneratorAdapter.EQ, falseLabel);
|
|
||||||
methodWriter.push(true);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
methodWriter.visitLabel(falseLabel);
|
|
||||||
methodWriter.push(false);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
methodWriter.visitMaxs(1, 1);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPart(PatchworkClassPart patchworkPart) throws GenerationException {
|
|
||||||
patchworkPart.fieldName("f_" + generateUniqueIdentifier());
|
|
||||||
|
|
||||||
classWriter.visitField(
|
|
||||||
Opcodes.ACC_PUBLIC,
|
|
||||||
patchworkPart.fieldName(),
|
|
||||||
patchworkPart.partInterface().descriptorString(),
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
appendPartMethods(patchworkPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendPartMethods(PatchworkClassPart patchworkPart) throws GenerationException {
|
|
||||||
try {
|
|
||||||
ClassReader classReader = new ClassReader(patchworkPart.partInterface().getName());
|
|
||||||
classReader.accept(new PartClassVisitor(patchworkPart), ClassReader.SKIP_FRAMES);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new GenerationException("Failed to read interface class file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// </editor-fold>
|
|
||||||
|
|
||||||
private GeneratorAdapter createMethod(
|
|
||||||
int access,
|
|
||||||
String name,
|
|
||||||
String desc,
|
|
||||||
@Nullable String signature,
|
|
||||||
String @Nullable [] exceptions
|
|
||||||
) {
|
|
||||||
MethodVisitor methodVisitor = classWriter.visitMethod(access, name, desc, signature, exceptions);
|
|
||||||
return new GeneratorAdapter(methodVisitor, access, name, desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void visitFieldInsn(MethodVisitor methodWriter, PatchworkClassPart part, int opcode) {
|
|
||||||
methodWriter.visitFieldInsn(
|
|
||||||
opcode,
|
|
||||||
internalClassName(),
|
|
||||||
part.fieldName(),
|
|
||||||
Type.getDescriptor(part.partInterface())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void visitToString(MethodVisitor methodWriter) {
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Object.class),
|
|
||||||
"toString",
|
|
||||||
"()Ljava/lang/String;",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void visitStringBuilderAppendString(MethodVisitor methodWriter) {
|
|
||||||
String stringBuilderType = Type.getInternalName(StringBuilder.class);
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKEVIRTUAL,
|
|
||||||
stringBuilderType,
|
|
||||||
"append",
|
|
||||||
"(Ljava/lang/String;)L" + stringBuilderType + ";",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PartClassVisitor extends ClassVisitor {
|
|
||||||
private final PatchworkClassPart extensionClass;
|
|
||||||
|
|
||||||
protected PartClassVisitor(PatchworkClassPart extensionClass) {
|
|
||||||
super(Opcodes.ASM9);
|
|
||||||
this.extensionClass = extensionClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MethodVisitor visitMethod(
|
|
||||||
int access,
|
|
||||||
String name,
|
|
||||||
String descriptor,
|
|
||||||
String signature,
|
|
||||||
String[] exceptions
|
|
||||||
) {
|
|
||||||
GeneratorAdapter methodWriter = createMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions);
|
|
||||||
return new PartMethodVisitor(api, methodWriter, descriptor, extensionClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PartMethodVisitor extends MethodVisitor {
|
|
||||||
private final GeneratorAdapter methodWriter;
|
|
||||||
private final String methodDescriptor;
|
|
||||||
private final PatchworkClassPart patchworkPart;
|
|
||||||
|
|
||||||
protected PartMethodVisitor(
|
|
||||||
int api,
|
|
||||||
GeneratorAdapter methodWriter,
|
|
||||||
String methodDescriptor,
|
|
||||||
PatchworkClassPart patchworkPart
|
|
||||||
) {
|
|
||||||
super(api);
|
|
||||||
this.methodWriter = methodWriter;
|
|
||||||
this.patchworkPart = patchworkPart;
|
|
||||||
this.methodDescriptor = methodDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitParameter(String name, int access) {
|
|
||||||
methodWriter.visitParameter(name, access);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitEnd() {
|
|
||||||
Label nullLabel = methodWriter.newLabel();
|
|
||||||
|
|
||||||
methodWriter.visitCode();
|
|
||||||
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
|
||||||
methodWriter.visitFieldInsn(
|
|
||||||
Opcodes.GETFIELD,
|
|
||||||
internalClassName(),
|
|
||||||
patchworkPart.fieldName(),
|
|
||||||
Type.getDescriptor(patchworkPart.partInterface())
|
|
||||||
);
|
|
||||||
methodWriter.dup();
|
|
||||||
methodWriter.ifNull(nullLabel);
|
|
||||||
methodWriter.loadArgs();
|
|
||||||
methodWriter.visitMethodInsn(
|
|
||||||
Opcodes.INVOKEINTERFACE,
|
|
||||||
Type.getInternalName(patchworkPart.partInterface()),
|
|
||||||
methodWriter.getName(),
|
|
||||||
methodDescriptor,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
methodWriter.returnValue();
|
|
||||||
methodWriter.visitLabel(nullLabel);
|
|
||||||
methodWriter.pop();
|
|
||||||
methodWriter.throwException(
|
|
||||||
Type.getType(PatchworkPartIsNullException.class),
|
|
||||||
"The patchwork part " + patchworkPart.partInterface().getSimpleName() + " has not been set"
|
|
||||||
);
|
|
||||||
|
|
||||||
methodWriter.visitMaxs(-1, -1);
|
|
||||||
methodWriter.visitEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
private static class MethodDescriptor {
|
|
||||||
String name;
|
|
||||||
Class<?>[] parameterTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class Config {
|
|
||||||
@lombok.NonNull
|
|
||||||
private @NonNull String classPackage;
|
|
||||||
private String classPrefix = "";
|
|
||||||
private Collection<Class<?>> markerInterfaces = Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class VerificationException extends Exception {
|
|
||||||
private VerificationException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public static class InvalidPatchworkPartClassException extends VerificationException {
|
|
||||||
Class<?> partClass;
|
|
||||||
|
|
||||||
public InvalidPatchworkPartClassException(Class<?> partClass, String message) {
|
|
||||||
super("Invalid patchwork part class " + partClass.getName() + ": " + message);
|
|
||||||
this.partClass = partClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public static class DuplicateMethodsException extends VerificationException {
|
|
||||||
transient Collection<Method> signatures;
|
|
||||||
|
|
||||||
private DuplicateMethodsException(Collection<Method> methods) {
|
|
||||||
super("Duplicate method signatures:\n" + methods.stream()
|
|
||||||
.map(DuplicateMethodsException::getMethodMessage)
|
|
||||||
.collect(Collectors.joining("\n")));
|
|
||||||
this.signatures = methods;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMethodMessage(Method method) {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder("\t- "
|
|
||||||
+ method.getDeclaringClass().getCanonicalName()
|
|
||||||
+ "#(");
|
|
||||||
for (Class<?> parameterType : method.getParameterTypes()) {
|
|
||||||
stringBuilder.append(parameterType.getCanonicalName());
|
|
||||||
stringBuilder.append(", ");
|
|
||||||
}
|
|
||||||
stringBuilder.append(")");
|
|
||||||
stringBuilder.append(method.getReturnType().getCanonicalName());
|
|
||||||
stringBuilder.append("\n");
|
|
||||||
return stringBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GenerationException extends Exception {
|
|
||||||
public GenerationException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class PatchworkClassPart {
|
|
||||||
private final Class<?> partInterface;
|
|
||||||
private String fieldName;
|
|
||||||
private MethodHandle fieldSetter;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package de.siphalor.tweed5.patchwork.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.patchwork.api.InvalidPatchworkAccessException;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class PatchworkFactoryImpl implements PatchworkFactory {
|
||||||
|
private final UUID factoryUuid;
|
||||||
|
private final Class<?>[] partClasses;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Patchwork create() {
|
||||||
|
return new PatchworkImpl(new Object[partClasses.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder implements PatchworkFactory.Builder {
|
||||||
|
private final UUID factoryUuid = UUID.randomUUID();
|
||||||
|
private final List<Class<?>> partClasses = new ArrayList<>();
|
||||||
|
private boolean built;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> PatchworkPartAccess<T> registerPart(Class<T> partClass) {
|
||||||
|
requireFresh();
|
||||||
|
partClasses.add(partClass);
|
||||||
|
return new PartAccessImpl<>(factoryUuid, partClasses.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PatchworkFactory build() {
|
||||||
|
requireFresh();
|
||||||
|
built = true;
|
||||||
|
return new PatchworkFactoryImpl(factoryUuid, partClasses.toArray(new Class<?>[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireFresh() {
|
||||||
|
if (built) {
|
||||||
|
throw new IllegalStateException("Builder has already been used.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
private static class PartAccessImpl<T extends @Nullable Object> implements PatchworkPartAccess<T> {
|
||||||
|
private final UUID factoryUuid;
|
||||||
|
private final int partIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@EqualsAndHashCode
|
||||||
|
private class PatchworkImpl implements Patchwork {
|
||||||
|
private final @Nullable Object[] partValues;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends @Nullable Object> T get(PatchworkPartAccess<T> access) throws InvalidPatchworkAccessException {
|
||||||
|
PartAccessImpl<T> castedAccess = validatePartAccess(access);
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) partValues[castedAccess.partIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends @Nullable Object> void set(PatchworkPartAccess<T> access, T value) throws InvalidPatchworkAccessException {
|
||||||
|
PartAccessImpl<T> castedAccess = validatePartAccess(access);
|
||||||
|
|
||||||
|
if (value != null && !partClasses[castedAccess.partIndex].isInstance(value)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"value " + value + " of type " + value.getClass().getName() +
|
||||||
|
" doesn't match registered value class " + partClasses[castedAccess.partIndex].getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
partValues[castedAccess.partIndex] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends @Nullable Object> PartAccessImpl<T> validatePartAccess(PatchworkPartAccess<T> access)
|
||||||
|
throws InvalidPatchworkAccessException {
|
||||||
|
if (!(access instanceof PartAccessImpl<?>)) {
|
||||||
|
throw new InvalidPatchworkAccessException("Part access is of incorrect class.");
|
||||||
|
} else if (((PartAccessImpl<?>) access).factoryUuid != factoryUuid) {
|
||||||
|
throw new InvalidPatchworkAccessException("Part access does not belong to the same patchwork factory.");
|
||||||
|
}
|
||||||
|
return (PartAccessImpl<T>) access;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Patchwork copy() {
|
||||||
|
return new PatchworkImpl(Arrays.copyOf(partValues, partValues.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl.util;
|
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class StreamUtils {
|
|
||||||
@SafeVarargs
|
|
||||||
public static <T> Stream<T> concat(Stream<T>... streams) {
|
|
||||||
Stream<T> result = Stream.empty();
|
|
||||||
for (Stream<T> stream : streams) {
|
|
||||||
result = Stream.concat(result, stream);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
|
||||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartIsNullException;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class PatchworkClassGeneratorGeneratedClassTest {
|
|
||||||
|
|
||||||
PatchworkClassPart partA;
|
|
||||||
PatchworkClassPart partB;
|
|
||||||
|
|
||||||
byte[] bytes;
|
|
||||||
Class<Patchwork<?>> patchworkClass;
|
|
||||||
Patchwork<?> patchwork;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
partA = new PatchworkClassPart(ExtensionA.class);
|
|
||||||
partB = new PatchworkClassPart(ExtensionB.class);
|
|
||||||
PatchworkClassGenerator generator = new PatchworkClassGenerator(
|
|
||||||
new PatchworkClassGenerator.Config("de.siphalor.tweed5.core.test")
|
|
||||||
.classPrefix("FullTest$")
|
|
||||||
.markerInterfaces(Collections.singletonList(MarkerInterface.class)),
|
|
||||||
Arrays.asList(partA, partB)
|
|
||||||
);
|
|
||||||
|
|
||||||
assertDoesNotThrow(generator::verify);
|
|
||||||
assertDoesNotThrow(generator::generate);
|
|
||||||
|
|
||||||
bytes = generator.emit();
|
|
||||||
//noinspection unchecked
|
|
||||||
patchworkClass = (Class<Patchwork<?>>) assertDoesNotThrow(() -> ByteArrayClassLoader.loadClass(null, bytes));
|
|
||||||
|
|
||||||
patchwork = createPatchworkInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Disabled("Dumping the class is only for testing purposes")
|
|
||||||
void dumpClass() {
|
|
||||||
try {
|
|
||||||
Path target = File.createTempFile("tweed5-patchwork", ".class").toPath();
|
|
||||||
try (OutputStream os = Files.newOutputStream(target)) {
|
|
||||||
os.write(bytes);
|
|
||||||
}
|
|
||||||
System.out.println("Dumped generated class to " + target);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
assertNull(e, "Must not throw exception");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void packageName() {
|
|
||||||
assertEquals("de.siphalor.tweed5.core.test", patchworkClass.getPackage().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void className() {
|
|
||||||
assertTrue(patchworkClass.getSimpleName().startsWith("FullTest$"), "Generated class name must start with prefix FullTest$, got " + patchworkClass.getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void implementsInterfaces() {
|
|
||||||
assertImplements(MarkerInterface.class, patchworkClass);
|
|
||||||
assertImplements(Patchwork.class, patchworkClass);
|
|
||||||
assertImplements(ExtensionA.class, patchworkClass);
|
|
||||||
assertImplements(ExtensionB.class, patchworkClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void toStringMethod() {
|
|
||||||
int defaultHashCode = System.identityHashCode(patchwork);
|
|
||||||
String stringResult = patchwork.toString();
|
|
||||||
assertFalse(stringResult.contains(Integer.toHexString(defaultHashCode)), "Expected toString not to be the default toString, got: " + stringResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void toStringContent() {
|
|
||||||
setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl());
|
|
||||||
((ExtensionA) patchwork).setText("Hello World!");
|
|
||||||
String stringResult = patchwork.toString();
|
|
||||||
assertTrue(stringResult.contains("Hello World!"), "Expected toString to contain the toString of its extensions, got: " + stringResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void hashCodeMethod() {
|
|
||||||
int emptyHashCode = patchwork.hashCode();
|
|
||||||
|
|
||||||
setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl());
|
|
||||||
int hashCodeWithA = patchwork.hashCode();
|
|
||||||
|
|
||||||
((ExtensionA) patchwork).setText("Hello World!");
|
|
||||||
int hashCodeWithAContent = patchwork.hashCode();
|
|
||||||
|
|
||||||
assertNotEquals(emptyHashCode, hashCodeWithA, "Expected hashCode to be different, got: " + emptyHashCode + " and " + hashCodeWithA);
|
|
||||||
assertNotEquals(emptyHashCode, hashCodeWithAContent, "Expected hashCode to be different, got: " + emptyHashCode + " and " + hashCodeWithAContent);
|
|
||||||
assertNotEquals(hashCodeWithA, hashCodeWithAContent, "Expected hashCode to be different, got: " + hashCodeWithA + " and " + hashCodeWithAContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SimplifiableAssertion")
|
|
||||||
@Test
|
|
||||||
void equalsMethod() {
|
|
||||||
//noinspection ConstantValue,SimplifiableAssertion
|
|
||||||
assertFalse(patchwork.equals(null));
|
|
||||||
|
|
||||||
//noinspection EqualsWithItself
|
|
||||||
assertTrue(patchwork.equals(patchwork));
|
|
||||||
|
|
||||||
Patchwork<?> other = createPatchworkInstance();
|
|
||||||
assertTrue(patchwork.equals(other));
|
|
||||||
assertTrue(other.equals(patchwork));
|
|
||||||
|
|
||||||
setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl());
|
|
||||||
assertFalse(patchwork.equals(other));
|
|
||||||
|
|
||||||
setFieldValue(other, partA.fieldName(), new ExtensionAImpl());
|
|
||||||
assertTrue(patchwork.equals(other));
|
|
||||||
assertTrue(other.equals(patchwork));
|
|
||||||
|
|
||||||
((ExtensionA) patchwork).setText("Hello 1!");
|
|
||||||
((ExtensionA) other).setText("Hello 2!");
|
|
||||||
assertFalse(patchwork.equals(other));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void copy() {
|
|
||||||
ExtensionAImpl aImpl = new ExtensionAImpl();
|
|
||||||
setFieldValue(patchwork, partA.fieldName(), aImpl);
|
|
||||||
|
|
||||||
Patchwork<?> copy = patchwork.copy();
|
|
||||||
Object aValue = getFieldValue(copy, partA.fieldName());
|
|
||||||
Object bValue = getFieldValue(copy, partB.fieldName());
|
|
||||||
|
|
||||||
assertSame(aImpl, aValue);
|
|
||||||
assertNull(bValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void isPartDefined() {
|
|
||||||
assertPartDefined(ExtensionA.class, patchwork);
|
|
||||||
assertPartDefined(ExtensionB.class, patchwork);
|
|
||||||
assertPartUndefined(ExtensionC.class, patchwork);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertPartDefined(Class<?> anInterface, Patchwork<?> patchwork) {
|
|
||||||
assertTrue(patchwork.isPatchworkPartDefined(anInterface), "Patchwork part " + anInterface.getName() + " must be defined");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertPartUndefined(Class<?> anInterface, Patchwork<?> patchwork) {
|
|
||||||
assertFalse(patchwork.isPatchworkPartDefined(anInterface), "Patchwork part " + anInterface.getName() + " must not be defined");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void checkFieldsExist() {
|
|
||||||
assertFieldExists(partA.fieldName(), ExtensionA.class);
|
|
||||||
assertFieldExists(partB.fieldName(), ExtensionB.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
void assertFieldExists(String fieldName, Class<?> fieldType) {
|
|
||||||
Field field = getField(fieldName);
|
|
||||||
assertEquals(fieldType, field.getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void isPartSet() {
|
|
||||||
assertPartUnset(ExtensionA.class, patchwork);
|
|
||||||
assertPartUnset(ExtensionB.class, patchwork);
|
|
||||||
assertPartUnset(ExtensionC.class, patchwork);
|
|
||||||
|
|
||||||
setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl());
|
|
||||||
|
|
||||||
assertPartSet(ExtensionA.class, patchwork);
|
|
||||||
assertPartUnset(ExtensionB.class, patchwork);
|
|
||||||
assertPartUnset(ExtensionC.class, patchwork);
|
|
||||||
|
|
||||||
setFieldValue(patchwork, partB.fieldName(), new ExtensionBImpl());
|
|
||||||
|
|
||||||
assertPartSet(ExtensionA.class, patchwork);
|
|
||||||
assertPartSet(ExtensionB.class, patchwork);
|
|
||||||
assertPartUnset(ExtensionC.class, patchwork);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void inheritedMethodCalls() {
|
|
||||||
assertThrows(PatchworkPartIsNullException.class, () -> ((ExtensionA) patchwork).getText());
|
|
||||||
assertThrows(PatchworkPartIsNullException.class, () -> ((ExtensionB) patchwork).multiply(1, 2));
|
|
||||||
|
|
||||||
setFieldValue(patchwork, partA.fieldName(), new ExtensionAImpl());
|
|
||||||
setFieldValue(patchwork, partB.fieldName(), new ExtensionBImpl());
|
|
||||||
|
|
||||||
assertEquals(6, assertDoesNotThrow(() -> ((ExtensionB) patchwork).multiply(2, 3)), "Method of extension b must be working");
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> ((ExtensionA) patchwork).setText("something"));
|
|
||||||
assertEquals("something", ((ExtensionA) patchwork).getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
Patchwork<?> createPatchworkInstance() {
|
|
||||||
Constructor<?> constructor = assertDoesNotThrow(() -> patchworkClass.getConstructor(), "The generated class' constructor must be accessible");
|
|
||||||
return assertDoesNotThrow(() -> (Patchwork<?>) constructor.newInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
Object getFieldValue(Patchwork<?> patchwork, String fieldName) {
|
|
||||||
return assertDoesNotThrow(() -> getField(fieldName).get(patchwork));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFieldValue(Patchwork<?> patchwork, String fieldName, Object value) {
|
|
||||||
assertDoesNotThrow(() -> getField(fieldName).set(patchwork, value), "Field " + fieldName + " must be accessible");
|
|
||||||
}
|
|
||||||
|
|
||||||
Field getField(String fieldName) {
|
|
||||||
return assertDoesNotThrow(() -> patchworkClass.getField(fieldName), "Field " + fieldName + " must exist and be public");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertPartSet(Class<?> anInterface, Patchwork<?> patchwork) {
|
|
||||||
assertTrue(patchwork.isPatchworkPartSet(anInterface), "Patchwork part " + anInterface.getName() + " must be set");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertPartUnset(Class<?> anInterface, Patchwork<?> patchwork) {
|
|
||||||
assertFalse(patchwork.isPatchworkPartSet(anInterface), "Patchwork part " + anInterface.getName() + " must not be set");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertImplements(Class<?> anInterface, Class<?> theClass) {
|
|
||||||
assertTrue(anInterface.isAssignableFrom(theClass), "Class " + theClass.getName() + " must implement " + anInterface.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface MarkerInterface {}
|
|
||||||
|
|
||||||
public interface ExtensionA {
|
|
||||||
String getText();
|
|
||||||
|
|
||||||
void setText(String text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Accessors(fluent = false)
|
|
||||||
static class ExtensionAImpl implements ExtensionA {
|
|
||||||
private String text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ExtensionB {
|
|
||||||
int multiply(int a, int b);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ExtensionBImpl implements ExtensionB {
|
|
||||||
public int multiply(int a, int b) {
|
|
||||||
return a * b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ExtensionC {
|
|
||||||
void test();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package de.siphalor.tweed5.patchwork.impl;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
class PatchworkClassGeneratorTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void notAnInterface() {
|
|
||||||
PatchworkClassGenerator generator = createGenerator(Collections.singletonList(NotAnInterface.class));
|
|
||||||
assertThrows(PatchworkClassGenerator.VerificationException.class, generator::verify);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void nonPublicInterface() {
|
|
||||||
PatchworkClassGenerator generator = createGenerator(Collections.singletonList(NonPublicInterface.class));
|
|
||||||
assertThrows(PatchworkClassGenerator.VerificationException.class, generator::verify);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void duplicateFields() {
|
|
||||||
PatchworkClassGenerator generator = createGenerator(Arrays.asList(DuplicateA.class, DuplicateB.class));
|
|
||||||
assertThrows(PatchworkClassGenerator.VerificationException.class, generator::verify);
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchworkClassGenerator createGenerator(Collection<Class<?>> partClasses) {
|
|
||||||
return new PatchworkClassGenerator(
|
|
||||||
new PatchworkClassGenerator.Config("de.siphalor.tweed5.patchwork.test.generated"),
|
|
||||||
partClasses.stream().map(PatchworkClassPart::new).collect(Collectors.toList())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class NotAnInterface {
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NonPublicInterface {
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DuplicateA {
|
|
||||||
void test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DuplicateB {
|
|
||||||
void test();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package de.siphalor.tweed5.patchwork.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.patchwork.api.InvalidPatchworkAccessException;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
class PatchworkFactoryImplTest {
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder();
|
||||||
|
PatchworkPartAccess<String> stringAccess1 = builder.registerPart(String.class);
|
||||||
|
PatchworkPartAccess<@Nullable Number> integerAccess1 = builder.registerPart(Number.class);
|
||||||
|
PatchworkPartAccess<String> stringAccess2 = builder.registerPart(String.class);
|
||||||
|
PatchworkFactory factory = builder.build();
|
||||||
|
|
||||||
|
Patchwork patchwork = factory.create();
|
||||||
|
|
||||||
|
assertThat(patchwork.get(stringAccess1)).isNull();
|
||||||
|
assertThat(patchwork.get(integerAccess1)).isNull();
|
||||||
|
assertThat(patchwork.get(stringAccess2)).isNull();
|
||||||
|
|
||||||
|
patchwork.set(stringAccess1, "Hello");
|
||||||
|
patchwork.set(stringAccess2, "World");
|
||||||
|
patchwork.set(integerAccess1, 123);
|
||||||
|
|
||||||
|
assertThat(patchwork.get(stringAccess1)).isEqualTo("Hello");
|
||||||
|
assertThat(patchwork.get(integerAccess1)).isEqualTo(123);
|
||||||
|
assertThat(patchwork.get(stringAccess2)).isEqualTo("World");
|
||||||
|
|
||||||
|
patchwork.set(integerAccess1, null);
|
||||||
|
assertThat(patchwork.get(integerAccess1)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void copy() {
|
||||||
|
PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder();
|
||||||
|
PatchworkPartAccess<String> stringAccess = builder.registerPart(String.class);
|
||||||
|
PatchworkFactory factory = builder.build();
|
||||||
|
|
||||||
|
Patchwork patchwork = factory.create();
|
||||||
|
patchwork.set(stringAccess, "Hello");
|
||||||
|
|
||||||
|
Patchwork copy = patchwork.copy();
|
||||||
|
assertThat(copy.get(stringAccess)).isEqualTo("Hello");
|
||||||
|
assertThat(copy).isEqualTo(patchwork);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lateRegister() {
|
||||||
|
PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder();
|
||||||
|
builder.build();
|
||||||
|
assertThatThrownBy(() -> builder.registerPart(String.class)).isInstanceOf(IllegalStateException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doubleBuild() {
|
||||||
|
PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder();
|
||||||
|
builder.build();
|
||||||
|
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void invalidAccess() {
|
||||||
|
PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder();
|
||||||
|
builder.registerPart(String.class);
|
||||||
|
PatchworkFactory factory = builder.build();
|
||||||
|
|
||||||
|
Patchwork patchwork = factory.create();
|
||||||
|
|
||||||
|
PatchworkFactoryImpl.Builder otherBuilder = new PatchworkFactoryImpl.Builder();
|
||||||
|
PatchworkPartAccess<String> otherAccess = otherBuilder.registerPart(String.class);
|
||||||
|
otherBuilder.build();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> patchwork.get(otherAccess)).isInstanceOf(InvalidPatchworkAccessException.class);
|
||||||
|
assertThatThrownBy(() -> patchwork.set(otherAccess, "Hello")).isInstanceOf(InvalidPatchworkAccessException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setWrongType() {
|
||||||
|
PatchworkFactoryImpl.Builder builder = new PatchworkFactoryImpl.Builder();
|
||||||
|
//noinspection unchecked
|
||||||
|
PatchworkPartAccess<Object> access = ((PatchworkPartAccess<Object>)(Object) builder.registerPart(String.class));
|
||||||
|
PatchworkFactory factory = builder.build();
|
||||||
|
|
||||||
|
Patchwork patchwork = factory.create();
|
||||||
|
assertThatThrownBy(() -> patchwork.set(access, 123)).isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package de.siphalor.tweed5.data.extension.api;
|
|
||||||
|
|
||||||
public interface EntryReaderWriterDefinition {
|
|
||||||
TweedEntryReader<?, ?> reader();
|
|
||||||
TweedEntryWriter<?, ?> writer();
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package de.siphalor.tweed5.data.extension.api;
|
|
||||||
|
|
||||||
public interface ReadWriteEntryDataExtension {
|
|
||||||
TweedEntryReader<?, ?> entryReaderChain();
|
|
||||||
TweedEntryWriter<?, ?> entryWriterChain();
|
|
||||||
}
|
|
||||||
@@ -2,29 +2,120 @@ 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.readwrite.TweedEntryReaderWriter;
|
||||||
import de.siphalor.tweed5.data.extension.impl.ReadWriteExtensionImpl;
|
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 de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public interface ReadWriteExtension extends TweedExtension {
|
public interface ReadWriteExtension extends TweedExtension {
|
||||||
Class<? extends ReadWriteExtension> DEFAULT = ReadWriteExtensionImpl.class;
|
Class<? extends ReadWriteExtension> DEFAULT = ReadWriteExtensionImpl.class;
|
||||||
|
|
||||||
void setEntryReaderWriterDefinition(ConfigEntry<?> entry, EntryReaderWriterDefinition readerWriterDefinition);
|
static <T> Consumer<ConfigEntry<T>> entryReaderWriter(
|
||||||
|
TweedEntryReaderWriter<T, ? extends ConfigEntry<T>> entryReaderWriter
|
||||||
|
) {
|
||||||
|
return entryReaderWriter(entryReaderWriter, entryReaderWriter);
|
||||||
|
}
|
||||||
|
|
||||||
ReadWriteContextExtensionsData createReadWriteContextExtensionsData();
|
static <T> Consumer<ConfigEntry<T>> entryReaderWriter(
|
||||||
|
TweedEntryReader<T, ? extends ConfigEntry<T>> entryReader,
|
||||||
|
TweedEntryWriter<T, ? extends ConfigEntry<T>> entryWriter
|
||||||
|
) {
|
||||||
|
return entry -> {
|
||||||
|
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
|
||||||
|
extension.setEntryReader(entry, entryReader);
|
||||||
|
extension.setEntryWriter(entry, entryWriter);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
<T extends @Nullable Object> T read(
|
static <T> Consumer<ConfigEntry<T>> entryReader(TweedEntryReader<T, ? extends ConfigEntry<T>> entryReader) {
|
||||||
|
return entry -> {
|
||||||
|
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
|
||||||
|
extension.setEntryReader(entry, entryReader);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Consumer<ConfigEntry<T>> entryWriter(TweedEntryWriter<T, ? extends ConfigEntry<T>> entryWriter) {
|
||||||
|
return entry -> {
|
||||||
|
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
|
||||||
|
extension.setEntryWriter(entry, entryWriter);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends @Nullable Object> Function<ConfigEntry<T>, T> read(TweedDataReader reader) {
|
||||||
|
return read(reader, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends @Nullable Object> Function<ConfigEntry<T>, T> read(
|
||||||
TweedDataReader reader,
|
TweedDataReader reader,
|
||||||
|
@Nullable Consumer<Patchwork> contextExtensionsDataCustomizer
|
||||||
|
) {
|
||||||
|
return entry -> {
|
||||||
|
try {
|
||||||
|
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
|
||||||
|
Patchwork contextExtensionsData = extension.createReadWriteContextExtensionsData();
|
||||||
|
if (contextExtensionsDataCustomizer != null) {
|
||||||
|
contextExtensionsDataCustomizer.accept(contextExtensionsData);
|
||||||
|
}
|
||||||
|
return extension.read(reader, entry, contextExtensionsData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends @Nullable Object> Consumer<ConfigEntry<T>> write(TweedDataVisitor writer, T value) {
|
||||||
|
return write(writer, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends @Nullable Object> Consumer<ConfigEntry<T>> write(
|
||||||
|
TweedDataVisitor writer,
|
||||||
|
T value,
|
||||||
|
@Nullable Consumer<Patchwork> contextExtensionsDataCustomizer
|
||||||
|
) {
|
||||||
|
return entry -> {
|
||||||
|
try {
|
||||||
|
ReadWriteExtension extension = entry.container().extension(ReadWriteExtension.class)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No ReadWriteExtension present"));
|
||||||
|
Patchwork contextExtensionsData = extension.createReadWriteContextExtensionsData();
|
||||||
|
if (contextExtensionsDataCustomizer != null) {
|
||||||
|
contextExtensionsDataCustomizer.accept(contextExtensionsData);
|
||||||
|
}
|
||||||
|
extension.write(writer, value, entry, contextExtensionsData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<T, C extends ConfigEntry<T>> void setEntryReaderWriter(
|
||||||
ConfigEntry<T> entry,
|
ConfigEntry<T> entry,
|
||||||
ReadWriteContextExtensionsData contextExtensionsData
|
TweedEntryReader<T, C> entryReader,
|
||||||
) throws TweedEntryReadException;
|
TweedEntryWriter<T, C> entryWriter
|
||||||
|
);
|
||||||
|
<T, C extends ConfigEntry<T>> void setEntryReader(ConfigEntry<T> entry, TweedEntryReader<T, C> entryReader);
|
||||||
|
<T, C extends ConfigEntry<T>> void setEntryWriter(ConfigEntry<T> entry, TweedEntryWriter<T, C> entryWriter);
|
||||||
|
|
||||||
|
Patchwork createReadWriteContextExtensionsData();
|
||||||
|
|
||||||
|
<T extends @Nullable Object> T read(TweedDataReader reader, ConfigEntry<T> entry, Patchwork contextExtensionsData)
|
||||||
|
throws TweedEntryReadException;
|
||||||
|
|
||||||
<T extends @Nullable Object> void write(
|
<T extends @Nullable Object> void write(
|
||||||
TweedDataVisitor writer,
|
TweedDataVisitor writer,
|
||||||
T value,
|
T value,
|
||||||
ConfigEntry<T> entry,
|
ConfigEntry<T> entry,
|
||||||
ReadWriteContextExtensionsData contextExtensionsData
|
Patchwork contextExtensionsData
|
||||||
) throws TweedEntryWriteException;
|
) throws TweedEntryWriteException;
|
||||||
|
|
||||||
|
<T, C extends ConfigEntry<T>> TweedEntryReader<T, C> getReaderChain(C entry);
|
||||||
|
<T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> getWriterChain(C entry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package de.siphalor.tweed5.data.extension.api;
|
package de.siphalor.tweed5.data.extension.api;
|
||||||
|
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
|
||||||
public interface TweedReadContext {
|
public interface TweedReadContext {
|
||||||
ReadWriteContextExtensionsData extensionsData();
|
ReadWriteExtension readWriteExtension();
|
||||||
|
Patchwork extensionsData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package de.siphalor.tweed5.data.extension.api;
|
package de.siphalor.tweed5.data.extension.api;
|
||||||
|
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
|
||||||
public interface TweedWriteContext {
|
public interface TweedWriteContext {
|
||||||
ReadWriteContextExtensionsData extensionsData();
|
ReadWriteExtension readWriteExtension();
|
||||||
|
Patchwork extensionsData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package de.siphalor.tweed5.data.extension.api.extension;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
|
||||||
|
|
||||||
public interface ReadWriteContextExtensionsData extends Patchwork<ReadWriteContextExtensionsData> {
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package de.siphalor.tweed5.data.extension.api.extension;
|
package de.siphalor.tweed5.data.extension.api.extension;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
|
|
||||||
public interface ReadWriteExtensionSetupContext {
|
public interface ReadWriteExtensionSetupContext {
|
||||||
<E> RegisteredExtensionData<ReadWriteContextExtensionsData, E> registerReadWriteContextExtensionData(Class<E> extensionDataClass);
|
<E> PatchworkPartAccess<E> registerReadWriteContextExtensionData(Class<E> extensionDataClass);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ package de.siphalor.tweed5.data.extension.api.readwrite;
|
|||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
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 org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
public interface TweedEntryReaderWriter<T, C extends ConfigEntry<T>> extends TweedEntryReader<T, C>, TweedEntryWriter<T, C> {}
|
public interface TweedEntryReaderWriter<T extends @Nullable Object, C extends ConfigEntry<T>> extends TweedEntryReader<T, C>, TweedEntryWriter<T, C> {}
|
||||||
|
|||||||
@@ -3,56 +3,41 @@ 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.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.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||||
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
||||||
import de.siphalor.tweed5.data.extension.api.*;
|
import de.siphalor.tweed5.data.extension.api.*;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
|
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
|
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
||||||
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.PatchworkFactory;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
import lombok.Data;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.Value;
|
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.jspecify.annotations.NonNull;
|
import org.jspecify.annotations.NonNull;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@AutoService(ReadWriteExtension.class)
|
@AutoService(ReadWriteExtension.class)
|
||||||
public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||||
private final ConfigContainer<?> configContainer;
|
private final ConfigContainer<?> configContainer;
|
||||||
private final RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>
|
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||||
readerWriterDefinitionExtension;
|
|
||||||
private final RegisteredExtensionData<EntryExtensionsData, ReadWriteEntryDataExtension> readWriteEntryDataExtension;
|
|
||||||
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>>
|
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>>
|
||||||
entryReaderMiddlewareContainer
|
entryReaderMiddlewareContainer
|
||||||
= new DefaultMiddlewareContainer<>();
|
= new DefaultMiddlewareContainer<>();
|
||||||
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>>
|
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>>
|
||||||
entryWriterMiddlewareContainer
|
entryWriterMiddlewareContainer
|
||||||
= new DefaultMiddlewareContainer<>();
|
= new DefaultMiddlewareContainer<>();
|
||||||
private final Map<Class<?>, RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>>
|
private @Nullable PatchworkFactory readWriteContextPatchworkFactory;
|
||||||
readWriteContextExtensionsDataClasses
|
|
||||||
= new HashMap<>();
|
|
||||||
private @Nullable PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork;
|
|
||||||
|
|
||||||
public ReadWriteExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
public ReadWriteExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
||||||
this.configContainer = configContainer;
|
this.configContainer = configContainer;
|
||||||
this.readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class);
|
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||||
this.readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,23 +49,8 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
|||||||
public void extensionsFinalized() {
|
public void extensionsFinalized() {
|
||||||
Collection<TweedExtension> extensions = configContainer.extensions();
|
Collection<TweedExtension> extensions = configContainer.extensions();
|
||||||
|
|
||||||
ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() {
|
PatchworkFactory.Builder readWriteContextPatchworkFactorBuilder = PatchworkFactory.builder();
|
||||||
@Override
|
ReadWriteExtensionSetupContext setupContext = readWriteContextPatchworkFactorBuilder::registerPart;
|
||||||
public <E> RegisteredExtensionData<ReadWriteContextExtensionsData, E> registerReadWriteContextExtensionData(
|
|
||||||
Class<E> extensionDataClass
|
|
||||||
) {
|
|
||||||
if (readWriteContextExtensionsDataClasses.containsKey(extensionDataClass)) {
|
|
||||||
throw new IllegalArgumentException("Context extension "
|
|
||||||
+ extensionDataClass.getName()
|
|
||||||
+ " is already registered");
|
|
||||||
}
|
|
||||||
RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, E>
|
|
||||||
registeredExtensionData
|
|
||||||
= new RegisteredExtensionDataImpl<>();
|
|
||||||
readWriteContextExtensionsDataClasses.put(extensionDataClass, registeredExtensionData);
|
|
||||||
return registeredExtensionData;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
entryReaderMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
entryReaderMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
||||||
entryWriterMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
entryWriterMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
||||||
@@ -102,79 +72,62 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readWriteContextPatchworkFactory = readWriteContextPatchworkFactorBuilder.build();
|
||||||
|
|
||||||
entryReaderMiddlewareContainer.seal();
|
entryReaderMiddlewareContainer.seal();
|
||||||
entryWriterMiddlewareContainer.seal();
|
entryWriterMiddlewareContainer.seal();
|
||||||
|
}
|
||||||
|
|
||||||
val patchworkClassCreator = PatchworkClassCreator.<ReadWriteContextExtensionsData>builder()
|
@Override
|
||||||
.patchworkInterface(ReadWriteContextExtensionsData.class)
|
public <T, C extends ConfigEntry<T>> void setEntryReaderWriter(
|
||||||
.classPackage("de.siphalor.tweed5.data.extension.generated")
|
ConfigEntry<T> entry,
|
||||||
.classPrefix("ReadWriteContextExtensionsData$")
|
TweedEntryReader<T, C> entryReader,
|
||||||
.build();
|
TweedEntryWriter<T, C> entryWriter
|
||||||
|
) {
|
||||||
|
CustomEntryData customEntryData = getOrCreateCustomEntryData(entry);
|
||||||
|
customEntryData.readerDefinition(entryReader);
|
||||||
|
customEntryData.writerDefinition(entryWriter);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
@Override
|
||||||
readWriteContextExtensionsDataPatchwork =
|
public <T, C extends ConfigEntry<T>> void setEntryReader(ConfigEntry<T> entry, TweedEntryReader<T, C> entryReader) {
|
||||||
patchworkClassCreator.createClass(readWriteContextExtensionsDataClasses.keySet());
|
getOrCreateCustomEntryData(entry).readerDefinition(entryReader);
|
||||||
for (PatchworkClassPart patchworkClassPart : readWriteContextExtensionsDataPatchwork.parts()) {
|
}
|
||||||
RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>
|
|
||||||
registeredExtension
|
@Override
|
||||||
= readWriteContextExtensionsDataClasses.get(patchworkClassPart.partInterface());
|
public <T, C extends ConfigEntry<T>> void setEntryWriter(ConfigEntry<T> entry, TweedEntryWriter<T, C> entryWriter) {
|
||||||
registeredExtension.setter = patchworkClassPart.fieldSetter();
|
getOrCreateCustomEntryData(entry).writerDefinition(entryWriter);
|
||||||
}
|
|
||||||
} catch (PatchworkClassGenerator.GenerationException e) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Failed to generate read write context extensions' data patchwork class",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initEntry(ConfigEntry<?> configEntry) {
|
public void initEntry(ConfigEntry<?> configEntry) {
|
||||||
TweedEntryReader<?, ?> baseReader;
|
CustomEntryData customEntryData = getOrCreateCustomEntryData(configEntry);
|
||||||
TweedEntryWriter<?, ?> baseWriter;
|
customEntryData.readerChain(entryReaderMiddlewareContainer.process(customEntryData.readerDefinition()));
|
||||||
if (configEntry.extensionsData().isPatchworkPartSet(EntryReaderWriterDefinition.class)) {
|
customEntryData.writerChain(entryWriterMiddlewareContainer.process(customEntryData.writerDefinition()));
|
||||||
EntryReaderWriterDefinition rwDefintion = (EntryReaderWriterDefinition) configEntry.extensionsData();
|
}
|
||||||
baseReader = rwDefintion.reader();
|
|
||||||
baseWriter = rwDefintion.writer();
|
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
|
||||||
} else {
|
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||||
baseReader = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
if (entryData == null) {
|
||||||
baseWriter = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
entryData = new CustomEntryData();
|
||||||
|
entry.extensionsData().set(customEntryDataAccess, entryData);
|
||||||
}
|
}
|
||||||
|
return entryData;
|
||||||
readWriteEntryDataExtension.set(
|
|
||||||
configEntry.extensionsData(), new ReadWriteEntryDataExtensionImpl(
|
|
||||||
entryReaderMiddlewareContainer.process(baseReader),
|
|
||||||
entryWriterMiddlewareContainer.process(baseWriter)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEntryReaderWriterDefinition(
|
public Patchwork createReadWriteContextExtensionsData() {
|
||||||
@NonNull ConfigEntry<?> entry,
|
assert readWriteContextPatchworkFactory != null;
|
||||||
@NonNull EntryReaderWriterDefinition readerWriterDefinition
|
return readWriteContextPatchworkFactory.create();
|
||||||
) {
|
|
||||||
readerWriterDefinitionExtension.set(entry.extensionsData(), readerWriterDefinition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ReadWriteContextExtensionsData createReadWriteContextExtensionsData() {
|
|
||||||
try {
|
|
||||||
assert readWriteContextExtensionsDataPatchwork != null;
|
|
||||||
return (ReadWriteContextExtensionsData) readWriteContextExtensionsDataPatchwork.constructor().invoke();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new IllegalStateException("Failed to instantiate read write context extensions' data", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends @Nullable Object> T read(
|
public <T extends @Nullable Object> T read(
|
||||||
@NonNull TweedDataReader reader,
|
TweedDataReader reader,
|
||||||
@NonNull ConfigEntry<T> entry,
|
ConfigEntry<T> entry,
|
||||||
@NonNull ReadWriteContextExtensionsData contextExtensionsData
|
Patchwork contextExtensionsData
|
||||||
) throws TweedEntryReadException {
|
) throws TweedEntryReadException {
|
||||||
try {
|
try {
|
||||||
return getReaderChain(entry).read(reader, entry, new TweedReadWriteContextImpl(contextExtensionsData));
|
return getReaderChain(entry).read(reader, entry, new TweedReadWriteContextImpl(this, contextExtensionsData));
|
||||||
} catch (TweedDataReadException e) {
|
} catch (TweedDataReadException e) {
|
||||||
throw new TweedEntryReadException("Failed to read entry", e);
|
throw new TweedEntryReadException("Failed to read entry", e);
|
||||||
}
|
}
|
||||||
@@ -182,46 +135,35 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends @Nullable Object> void write(
|
public <T extends @Nullable Object> void write(
|
||||||
@NonNull TweedDataVisitor writer,
|
TweedDataVisitor writer,
|
||||||
@Nullable T value,
|
@Nullable T value,
|
||||||
@NonNull ConfigEntry<T> entry,
|
ConfigEntry<T> entry,
|
||||||
@NonNull ReadWriteContextExtensionsData contextExtensionsData
|
Patchwork contextExtensionsData
|
||||||
) throws TweedEntryWriteException {
|
) throws TweedEntryWriteException {
|
||||||
try {
|
try {
|
||||||
getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(contextExtensionsData));
|
getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(this, contextExtensionsData));
|
||||||
} catch (TweedDataWriteException e) {
|
} catch (TweedDataWriteException e) {
|
||||||
throw new TweedEntryWriteException("Failed to write entry", e);
|
throw new TweedEntryWriteException("Failed to write entry", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Data
|
||||||
private static class ReadWriteEntryDataExtensionImpl implements ReadWriteEntryDataExtension {
|
private static class CustomEntryData {
|
||||||
TweedEntryReader<?, ?> entryReaderChain;
|
private TweedEntryReader<?, ?> readerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
||||||
TweedEntryWriter<?, ?> entryWriterChain;
|
private TweedEntryWriter<?, ?> writerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
||||||
|
private TweedEntryReader<?, ?> readerChain;
|
||||||
|
private TweedEntryWriter<?, ?> writerChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Setter
|
@Override
|
||||||
private static class RegisteredExtensionDataImpl<U extends Patchwork<@NonNull U>, E>
|
public <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> getReaderChain(C entry) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> TweedEntryReader<T, @NonNull ConfigEntry<T>> getReaderChain(ConfigEntry<T> elementEntry) {
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (TweedEntryReader<T, @NonNull ConfigEntry<T>>) ((ReadWriteEntryDataExtension) elementEntry.extensionsData()).entryReaderChain();
|
return (TweedEntryReader<T, C>) entry.extensionsData().get(customEntryDataAccess).readerChain();
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T> TweedEntryWriter<T, @NonNull ConfigEntry<T>> getWriterChain(ConfigEntry<T> elementEntry) {
|
@Override
|
||||||
|
public <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> getWriterChain(C entry) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (TweedEntryWriter<T, @NonNull ConfigEntry<T>>) ((ReadWriteEntryDataExtension) elementEntry.extensionsData()).entryWriterChain();
|
return (TweedEntryWriter<T, C>) entry.extensionsData().get(customEntryDataAccess).writerChain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import lombok.AccessLevel;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jspecify.annotations.NonNull;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -63,7 +62,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
private static class PrimitiveReaderWriter<T extends @NonNull Object> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
|
private static class PrimitiveReaderWriter<T extends @Nullable Object> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
|
||||||
private final Function<TweedDataToken, T> readerCall;
|
private final Function<TweedDataToken, T> readerCall;
|
||||||
private final BiConsumer<TweedDataVisitor, T> writerCall;
|
private final BiConsumer<TweedDataVisitor, T> writerCall;
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CollectionReaderWriter<T extends @NonNull Object, C extends Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> {
|
public static class CollectionReaderWriter<T extends @Nullable Object, C extends Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> {
|
||||||
@Override
|
@Override
|
||||||
public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
||||||
assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start");
|
assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start");
|
||||||
@@ -89,9 +88,9 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConfigEntry<T> elementEntry = entry.elementEntry();
|
ConfigEntry<T> elementEntry = entry.elementEntry();
|
||||||
TweedEntryReader<T, ConfigEntry<T>> elementReader = ReadWriteExtensionImpl.getReaderChain(elementEntry);
|
TweedEntryReader<T, ConfigEntry<T>> elementReader = context.readWriteExtension().getReaderChain(elementEntry);
|
||||||
|
|
||||||
List<T> list = new ArrayList<>(20);
|
List<@Nullable T> list = new ArrayList<>(20);
|
||||||
while (true) {
|
while (true) {
|
||||||
token = reader.peekToken();
|
token = reader.peekToken();
|
||||||
if (token.isListEnd()) {
|
if (token.isListEnd()) {
|
||||||
@@ -119,7 +118,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConfigEntry<T> elementEntry = entry.elementEntry();
|
ConfigEntry<T> elementEntry = entry.elementEntry();
|
||||||
TweedEntryWriter<T, ConfigEntry<T>> elementWriter = ReadWriteExtensionImpl.getWriterChain(elementEntry);
|
TweedEntryWriter<T, ConfigEntry<T>> elementWriter = context.readWriteExtension().getWriterChain(elementEntry);
|
||||||
|
|
||||||
writer.visitListStart();
|
writer.visitListStart();
|
||||||
for (T element : value) {
|
for (T element : value) {
|
||||||
@@ -129,7 +128,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CompoundReaderWriter<T extends @NonNull Object> implements TweedEntryReaderWriter<T, CompoundConfigEntry<T>> {
|
public static class CompoundReaderWriter<T> implements TweedEntryReaderWriter<T, CompoundConfigEntry<T>> {
|
||||||
@Override
|
@Override
|
||||||
public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
||||||
assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start");
|
assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start");
|
||||||
@@ -145,7 +144,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
|
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
|
||||||
TweedEntryReader<Object, ConfigEntry<Object>> subEntryReaderChain = ReadWriteExtensionImpl.getReaderChain(subEntry);
|
TweedEntryReader<Object, ConfigEntry<Object>> subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry);
|
||||||
Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context);
|
Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context);
|
||||||
entry.set(compoundValue, key, subEntryValue);
|
entry.set(compoundValue, key, subEntryValue);
|
||||||
} else {
|
} else {
|
||||||
@@ -167,7 +166,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
String key = e.getKey();
|
String key = e.getKey();
|
||||||
ConfigEntry<Object> subEntry = e.getValue();
|
ConfigEntry<Object> subEntry = e.getValue();
|
||||||
|
|
||||||
TweedEntryWriter<Object, ConfigEntry<Object>> subEntryWriterChain = ReadWriteExtensionImpl.getWriterChain(subEntry);
|
TweedEntryWriter<Object, ConfigEntry<Object>> subEntryWriterChain = context.readWriteExtension().getWriterChain(subEntry);
|
||||||
|
|
||||||
writer.visitMapEntryKey(key);
|
writer.visitMapEntryKey(key);
|
||||||
subEntryWriterChain.write(writer, entry.get(value, key), subEntry, context);
|
subEntryWriterChain.write(writer, entry.get(value, key), subEntry, context);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package de.siphalor.tweed5.data.extension.impl;
|
package de.siphalor.tweed5.data.extension.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
|
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
|
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
|
public class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
|
||||||
ReadWriteContextExtensionsData extensionsData;
|
ReadWriteExtension readWriteExtension;
|
||||||
|
Patchwork extensionsData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
package de.siphalor.tweed5.data.extension.impl;
|
package de.siphalor.tweed5.data.extension.impl;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||||
import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.CollectionConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
|
||||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
|
|
||||||
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.dataapi.api.TweedDataVisitor;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -27,6 +22,8 @@ import java.io.Writer;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
|
||||||
|
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||||
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -36,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
class ReadWriteExtensionImplTest {
|
class ReadWriteExtensionImplTest {
|
||||||
private final StringWriter stringWriter = new StringWriter();
|
private final StringWriter stringWriter = new StringWriter();
|
||||||
private final ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
|
private final ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
|
||||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -44,14 +41,16 @@ class ReadWriteExtensionImplTest {
|
|||||||
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
|
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
|
||||||
configContainer.finishExtensionSetup();
|
configContainer.finishExtensionSetup();
|
||||||
|
|
||||||
SimpleConfigEntryImpl<Integer> intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
SimpleConfigEntry<Integer> intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||||
SimpleConfigEntryImpl<Boolean> booleanEntry = new SimpleConfigEntryImpl<>(configContainer, Boolean.class);
|
.apply(entryReaderWriter(intReaderWriter()));
|
||||||
CollectionConfigEntryImpl<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>(
|
|
||||||
|
CollectionConfigEntry<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>(
|
||||||
configContainer,
|
configContainer,
|
||||||
(Class<List<Boolean>>) (Class<?>) List.class,
|
(Class<List<Boolean>>) (Class<?>) List.class,
|
||||||
ArrayList::new,
|
ArrayList::new,
|
||||||
booleanEntry
|
new SimpleConfigEntryImpl<>(configContainer, Boolean.class)
|
||||||
);
|
.apply(entryReaderWriter(booleanReaderWriter()))
|
||||||
|
).apply(entryReaderWriter(collectionReaderWriter()));
|
||||||
|
|
||||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||||
configContainer,
|
configContainer,
|
||||||
@@ -61,16 +60,9 @@ class ReadWriteExtensionImplTest {
|
|||||||
entry("int", intEntry),
|
entry("int", intEntry),
|
||||||
entry("list", listEntry)
|
entry("list", listEntry)
|
||||||
))
|
))
|
||||||
);
|
).apply(entryReaderWriter(compoundReaderWriter()));
|
||||||
|
|
||||||
configContainer.attachTree(rootEntry);
|
configContainer.attachTree(rootEntry);
|
||||||
|
|
||||||
RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
|
|
||||||
readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter()));
|
|
||||||
readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter()));
|
|
||||||
readerWriterData.set(listEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.collectionReaderWriter()));
|
|
||||||
readerWriterData.set(booleanEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.booleanReaderWriter()));
|
|
||||||
|
|
||||||
configContainer.initialize();
|
configContainer.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,19 +120,4 @@ class ReadWriteExtensionImplTest {
|
|||||||
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {
|
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {
|
||||||
return writerFactory.apply(stringWriter);
|
return writerFactory.apply(stringWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
private static class TrivialEntryReaderWriterDefinition implements EntryReaderWriterDefinition {
|
|
||||||
private final TweedEntryReaderWriter<?, ?> readerWriter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TweedEntryReader<?, ?> reader() {
|
|
||||||
return readerWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TweedEntryWriter<?, ?> writer() {
|
|
||||||
return readerWriter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,49 +74,32 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntryReaderWriterDefinition definition = createDefinitionFromEntryConfig(entryConfig, context);
|
//noinspection rawtypes,unchecked
|
||||||
if (definition != null) {
|
readWriteExtension.setEntryReaderWriter(
|
||||||
readWriteExtension.setEntryReaderWriterDefinition(configEntry, definition);
|
(ConfigEntry) configEntry,
|
||||||
}
|
(TweedEntryReader) resolveReader(entryConfig, context),
|
||||||
|
(TweedEntryWriter) resolveWriter(entryConfig, context)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable EntryReaderWriterDefinition createDefinitionFromEntryConfig(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
private TweedEntryReader<?, ?> resolveReader(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
||||||
String readerSpecText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
String specText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
||||||
String writerSpecText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer();
|
SerdePojoReaderWriterSpec spec = specFromText(specText, context);
|
||||||
|
|
||||||
SerdePojoReaderWriterSpec readerSpec;
|
|
||||||
SerdePojoReaderWriterSpec writerSpec;
|
|
||||||
if (readerSpecText.equals(writerSpecText)) {
|
|
||||||
readerSpec = writerSpec = specFromText(readerSpecText, context);
|
|
||||||
} else {
|
|
||||||
readerSpec = specFromText(readerSpecText, context);
|
|
||||||
writerSpec = specFromText(writerSpecText, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readerSpec == null && writerSpec == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection unchecked,rawtypes
|
//noinspection unchecked,rawtypes
|
||||||
TweedEntryReader<?, ?> reader = Optional.ofNullable(readerSpec)
|
return Optional.ofNullable(spec)
|
||||||
.map((spec) -> resolveReaderWriterFromSpec((Class<TweedEntryReader<?, ?>>)(Object) TweedEntryReader.class, readerFactories, spec, context))
|
.map(s -> resolveReaderWriterFromSpec((Class<TweedEntryReader<?, ?>>)(Object) TweedEntryReader.class, readerFactories, s, context))
|
||||||
.orElse(((TweedEntryReader) TweedEntryReaderWriterImpls.NOOP_READER_WRITER));
|
.orElse(((TweedEntryReader) TweedEntryReaderWriterImpls.NOOP_READER_WRITER));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TweedEntryWriter<?, ?> resolveWriter(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
||||||
|
String specText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer();
|
||||||
|
SerdePojoReaderWriterSpec spec = specFromText(specText, context);
|
||||||
|
|
||||||
//noinspection unchecked,rawtypes
|
//noinspection unchecked,rawtypes
|
||||||
TweedEntryWriter<?, ?> writer = Optional.ofNullable(writerSpec)
|
return Optional.ofNullable(spec)
|
||||||
.map((spec) -> resolveReaderWriterFromSpec((Class<TweedEntryWriter<?, ?>>)(Object) TweedEntryWriter.class, writerFactories, spec, context))
|
.map(s -> resolveReaderWriterFromSpec((Class<TweedEntryWriter<?, ?>>)(Object) TweedEntryWriter.class, writerFactories, s, context))
|
||||||
.orElse(((TweedEntryWriter) TweedEntryReaderWriterImpls.NOOP_READER_WRITER));
|
.orElse(((TweedEntryWriter) TweedEntryReaderWriterImpls.NOOP_READER_WRITER));
|
||||||
|
|
||||||
return new EntryReaderWriterDefinition() {
|
|
||||||
@Override
|
|
||||||
public TweedEntryReader<?, ?> reader() {
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TweedEntryWriter<?, ?> writer() {
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable SerdePojoReaderWriterSpec specFromText(String specText, WeavingContext context) {
|
private @Nullable SerdePojoReaderWriterSpec specFromText(String specText, WeavingContext context) {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package de.siphalor.tweed5.weaver.pojo.api.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.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CollectionWeaving;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.CollectionWeaving;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
||||||
@@ -24,24 +25,27 @@ public class CollectionPojoWeaver implements TweedPojoWeaver {
|
|||||||
.collectionEntryClass(CollectionConfigEntryImpl.class)
|
.collectionEntryClass(CollectionConfigEntryImpl.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private RegisteredExtensionData<WeavingContext.ExtensionsData, CollectionWeavingConfig> weavingConfigAccess;
|
@Nullable
|
||||||
|
private PatchworkPartAccess<CollectionWeavingConfig> weavingConfigAccess;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setup(SetupContext context) {
|
public void setup(SetupContext context) {
|
||||||
this.weavingConfigAccess = context.registerWeavingContextExtensionData(CollectionWeavingConfig.class);
|
this.weavingConfigAccess = context.registerWeavingContextExtensionData(CollectionWeavingConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"unchecked"})
|
||||||
@Override
|
@Override
|
||||||
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
|
assert weavingConfigAccess != null;
|
||||||
|
|
||||||
List<ActualType<?>> collectionTypeParams = valueType.getTypesOfSuperArguments(Collection.class);
|
List<ActualType<?>> collectionTypeParams = valueType.getTypesOfSuperArguments(Collection.class);
|
||||||
if (collectionTypeParams == null) {
|
if (collectionTypeParams == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
CollectionWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
CollectionWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
Patchwork newExtensionsData = context.extensionsData().copy();
|
||||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
newExtensionsData.set(weavingConfigAccess, weavingConfig);
|
||||||
|
|
||||||
IntFunction<Collection<Object>> constructor = getCollectionConstructor(valueType);
|
IntFunction<Collection<Object>> constructor = getCollectionConstructor(valueType);
|
||||||
|
|
||||||
@@ -66,10 +70,10 @@ public class CollectionPojoWeaver implements TweedPojoWeaver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CollectionWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
private CollectionWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||||
CollectionWeavingConfig parent;
|
assert weavingConfigAccess != null;
|
||||||
if (context.extensionsData().isPatchworkPartSet(CollectionWeavingConfig.class)) {
|
|
||||||
parent = (CollectionWeavingConfig) context.extensionsData();
|
CollectionWeavingConfig parent = context.extensionsData().get(weavingConfigAccess);
|
||||||
} else {
|
if (parent == null) {
|
||||||
parent = DEFAULT_WEAVING_CONFIG;
|
parent = DEFAULT_WEAVING_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package de.siphalor.tweed5.weaver.pojo.api.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.namingformat.api.NamingFormat;
|
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||||
import de.siphalor.tweed5.namingformat.api.NamingFormatCollector;
|
import de.siphalor.tweed5.namingformat.api.NamingFormatCollector;
|
||||||
import de.siphalor.tweed5.namingformat.api.NamingFormats;
|
import de.siphalor.tweed5.namingformat.api.NamingFormats;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
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;
|
||||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
@@ -22,7 +23,6 @@ import java.lang.invoke.MethodHandle;
|
|||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -37,7 +37,8 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final NamingFormatCollector namingFormatCollector = new NamingFormatCollector();
|
private final NamingFormatCollector namingFormatCollector = new NamingFormatCollector();
|
||||||
private RegisteredExtensionData<WeavingContext.ExtensionsData, CompoundWeavingConfig> weavingConfigAccess;
|
@Nullable
|
||||||
|
private PatchworkPartAccess<CompoundWeavingConfig> weavingConfigAccess;
|
||||||
|
|
||||||
public void setup(SetupContext context) {
|
public void setup(SetupContext context) {
|
||||||
namingFormatCollector.setupFormats();
|
namingFormatCollector.setupFormats();
|
||||||
@@ -47,13 +48,15 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
|
assert weavingConfigAccess != null;
|
||||||
|
|
||||||
if (context.annotations().getAnnotation(CompoundWeaving.class) == null) {
|
if (context.annotations().getAnnotation(CompoundWeaving.class) == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
CompoundWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
CompoundWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
Patchwork newExtensionsData = context.extensionsData().copy();
|
||||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
newExtensionsData.set(weavingConfigAccess, weavingConfig);
|
||||||
|
|
||||||
PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType());
|
PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType());
|
||||||
|
|
||||||
@@ -69,10 +72,10 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CompoundWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
private CompoundWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||||
CompoundWeavingConfig parent;
|
assert weavingConfigAccess != null;
|
||||||
if (context.extensionsData().isPatchworkPartSet(CompoundWeavingConfig.class)) {
|
|
||||||
parent = (CompoundWeavingConfig) context.extensionsData();
|
CompoundWeavingConfig parent = context.extensionsData().get(weavingConfigAccess);
|
||||||
} else {
|
if (parent == null) {
|
||||||
parent = DEFAULT_WEAVING_CONFIG;
|
parent = DEFAULT_WEAVING_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,10 +146,14 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
|
|
||||||
private WeavableCompoundConfigEntry.SubEntry weaveCompoundSubEntry(
|
private WeavableCompoundConfigEntry.SubEntry weaveCompoundSubEntry(
|
||||||
PojoClassIntrospector.Property property,
|
PojoClassIntrospector.Property property,
|
||||||
WeavingContext.ExtensionsData newExtensionsData,
|
Patchwork newExtensionsData,
|
||||||
WeavingContext parentContext
|
WeavingContext parentContext
|
||||||
) {
|
) {
|
||||||
String name = convertName(property.field().getName(), (CompoundWeavingConfig) newExtensionsData);
|
assert weavingConfigAccess != null;
|
||||||
|
CompoundWeavingConfig weavingConfig = newExtensionsData.get(weavingConfigAccess);
|
||||||
|
assert weavingConfig != null;
|
||||||
|
|
||||||
|
String name = convertName(property.field().getName(), weavingConfig);
|
||||||
WeavingContext subContext = createSubContextForProperty(property, name, newExtensionsData, parentContext);
|
WeavingContext subContext = createSubContextForProperty(property, name, newExtensionsData, parentContext);
|
||||||
|
|
||||||
ConfigEntry<?> subEntry;
|
ConfigEntry<?> subEntry;
|
||||||
@@ -189,7 +196,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
private WeavingContext createSubContextForProperty(
|
private WeavingContext createSubContextForProperty(
|
||||||
PojoClassIntrospector.Property property,
|
PojoClassIntrospector.Property property,
|
||||||
String name,
|
String name,
|
||||||
WeavingContext.ExtensionsData newExtensionsData,
|
Patchwork newExtensionsData,
|
||||||
WeavingContext parentContext
|
WeavingContext parentContext
|
||||||
) {
|
) {
|
||||||
return parentContext.subContextBuilder(name)
|
return parentContext.subContextBuilder(name)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||||
|
|
||||||
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
|
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||||
|
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
public interface TweedPojoWeaver extends TweedPojoWeavingFunction {
|
public interface TweedPojoWeaver extends TweedPojoWeavingFunction {
|
||||||
@@ -11,6 +12,6 @@ public interface TweedPojoWeaver extends TweedPojoWeavingFunction {
|
|||||||
void setup(SetupContext context);
|
void setup(SetupContext context);
|
||||||
|
|
||||||
interface SetupContext {
|
interface SetupContext {
|
||||||
<E> RegisteredExtensionData<WeavingContext.ExtensionsData, E> registerWeavingContextExtensionData(Class<E> dataClass);
|
<E> PatchworkPartAccess<E> registerWeavingContextExtensionData(Class<E> dataClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
|||||||
TweedPojoWeavingFunction.NonNull weavingFunction;
|
TweedPojoWeavingFunction.NonNull weavingFunction;
|
||||||
ConfigContainer<?> configContainer;
|
ConfigContainer<?> configContainer;
|
||||||
String[] path;
|
String[] path;
|
||||||
ExtensionsData extensionsData;
|
Patchwork extensionsData;
|
||||||
AnnotatedElement annotations;
|
AnnotatedElement annotations;
|
||||||
|
|
||||||
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) {
|
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) {
|
||||||
@@ -40,8 +40,6 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
|||||||
return weavingFunction.weaveEntry(valueType, context);
|
return weavingFunction.weaveEntry(valueType, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ExtensionsData extends Patchwork<ExtensionsData> {}
|
|
||||||
|
|
||||||
@Accessors(fluent = true, chain = true)
|
@Accessors(fluent = true, chain = true)
|
||||||
@Setter
|
@Setter
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
@@ -51,7 +49,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
|||||||
private final TweedPojoWeavingFunction.NonNull weavingFunction;
|
private final TweedPojoWeavingFunction.NonNull weavingFunction;
|
||||||
private final ConfigContainer<?> configContainer;
|
private final ConfigContainer<?> configContainer;
|
||||||
private final String[] path;
|
private final String[] path;
|
||||||
private ExtensionsData extensionsData;
|
private Patchwork extensionsData;
|
||||||
private AnnotatedElement annotations;
|
private AnnotatedElement annotations;
|
||||||
|
|
||||||
public WeavingContext build() {
|
public WeavingContext build() {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull T deepCopy(@NotNull T value) {
|
public @NotNull T deepCopy(T value) {
|
||||||
T copy = instantiateCollection(value.size());
|
T copy = instantiateCollection(value.size());
|
||||||
for (E element : value) {
|
for (E element : value) {
|
||||||
copy.add(elementEntry.deepCopy(element));
|
copy.add(elementEntry.deepCopy(element));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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.entry.ConfigEntryVisitor;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -97,7 +98,7 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
public void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) {
|
||||||
if (visitor.enterCompoundEntry(this, value)) {
|
if (visitor.enterCompoundEntry(this, value)) {
|
||||||
subEntries.forEach((key, entry) -> {
|
subEntries.forEach((key, entry) -> {
|
||||||
if (visitor.enterCompoundSubEntry(key)) {
|
if (visitor.enterCompoundSubEntry(key)) {
|
||||||
|
|||||||
@@ -2,22 +2,21 @@ 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.patchwork.api.Patchwork;
|
||||||
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||||
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.weaver.pojo.api.annotation.PojoWeaving;
|
|
||||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
|
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.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
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.util.Arrays;
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,7 +30,8 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
private final ConfigContainer<T> configContainer;
|
private final ConfigContainer<T> configContainer;
|
||||||
private final Collection<TweedPojoWeaver> weavers;
|
private final Collection<TweedPojoWeaver> weavers;
|
||||||
private final Collection<TweedPojoWeavingPostProcessor> postProcessors;
|
private final Collection<TweedPojoWeavingPostProcessor> postProcessors;
|
||||||
private PatchworkClass<WeavingContext.ExtensionsData> contextExtensionsDataClass;
|
@Nullable
|
||||||
|
private PatchworkFactory contextExtensionsPatchworkFactory;
|
||||||
|
|
||||||
public static <T> TweedPojoWeaverBootstrapper<T> create(Class<T> pojoClass) {
|
public static <T> TweedPojoWeaverBootstrapper<T> create(Class<T> pojoClass) {
|
||||||
PojoWeaving rootWeavingConfig = expectAnnotation(pojoClass, PojoWeaving.class);
|
PojoWeaving rootWeavingConfig = expectAnnotation(pojoClass, PojoWeaving.class);
|
||||||
@@ -88,44 +88,21 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupWeavers() {
|
private void setupWeavers() {
|
||||||
Map<Class<?>, RegisteredExtensionDataImpl<?>> registeredExtensions = new HashMap<>();
|
PatchworkFactory.Builder contextExtensionsPatchworkFactoryBuilder = PatchworkFactory.builder();
|
||||||
|
|
||||||
TweedPojoWeaver.SetupContext setupContext = new TweedPojoWeaver.SetupContext() {
|
TweedPojoWeaver.SetupContext setupContext = contextExtensionsPatchworkFactoryBuilder::registerPart;
|
||||||
@Override
|
|
||||||
public <E> RegisteredExtensionData<WeavingContext.ExtensionsData, E> registerWeavingContextExtensionData(
|
|
||||||
Class<E> dataClass
|
|
||||||
) {
|
|
||||||
RegisteredExtensionDataImpl<E> registeredExtension = new RegisteredExtensionDataImpl<>();
|
|
||||||
registeredExtensions.put(dataClass, registeredExtension);
|
|
||||||
return registeredExtension;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (TweedPojoWeaver weaver : weavers) {
|
for (TweedPojoWeaver weaver : weavers) {
|
||||||
weaver.setup(setupContext);
|
weaver.setup(setupContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchworkClassCreator<WeavingContext.ExtensionsData> weavingContextCreator = PatchworkClassCreator.<WeavingContext.ExtensionsData>builder()
|
contextExtensionsPatchworkFactory = contextExtensionsPatchworkFactoryBuilder.build();
|
||||||
.classPackage(this.getClass().getPackage().getName() + ".generated")
|
|
||||||
.classPrefix("WeavingContext$")
|
|
||||||
.patchworkInterface(WeavingContext.ExtensionsData.class)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.contextExtensionsDataClass = weavingContextCreator.createClass(registeredExtensions.keySet());
|
|
||||||
|
|
||||||
for (PatchworkClassPart part : this.contextExtensionsDataClass.parts()) {
|
|
||||||
RegisteredExtensionDataImpl<?> registeredExtension = registeredExtensions.get(part.partInterface());
|
|
||||||
registeredExtension.setter(part.fieldSetter());
|
|
||||||
}
|
|
||||||
} catch (PatchworkClassGenerator.GenerationException e) {
|
|
||||||
throw new PojoWeavingException("Failed to create weaving context extensions data");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WeavingContext createWeavingContext() {
|
private WeavingContext createWeavingContext() {
|
||||||
try {
|
try {
|
||||||
WeavingContext.ExtensionsData extensionsData = (WeavingContext.ExtensionsData) contextExtensionsDataClass.constructor().invoke();
|
assert contextExtensionsPatchworkFactory != null;
|
||||||
|
Patchwork extensionsData = contextExtensionsPatchworkFactory.create();
|
||||||
|
|
||||||
return WeavingContext.builder(this::weaveEntry, configContainer)
|
return WeavingContext.builder(this::weaveEntry, configContainer)
|
||||||
.extensionsData(extensionsData)
|
.extensionsData(extensionsData)
|
||||||
@@ -157,18 +134,4 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Setter
|
|
||||||
private static class RegisteredExtensionDataImpl<E> implements RegisteredExtensionData<WeavingContext.ExtensionsData, E> {
|
|
||||||
private MethodHandle setter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void set(WeavingContext.ExtensionsData patchwork, E extension) {
|
|
||||||
try {
|
|
||||||
setter.invokeWithArguments(patchwork, extension);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,18 @@ package de.siphalor.tweed5.weaver.pojo.api.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.entry.SimpleConfigEntry;
|
|
||||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
|
||||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.jspecify.annotations.NullUnmarked;
|
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;
|
||||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@NullUnmarked
|
@NullUnmarked
|
||||||
@@ -27,13 +21,12 @@ class CompoundPojoWeaverTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void weave() {
|
void weave() {
|
||||||
|
PatchworkFactory.Builder weavingContextExtensionDataFactoryBuilder = PatchworkFactory.builder();
|
||||||
|
|
||||||
CompoundPojoWeaver compoundWeaver = new CompoundPojoWeaver();
|
CompoundPojoWeaver compoundWeaver = new CompoundPojoWeaver();
|
||||||
compoundWeaver.setup(new TweedPojoWeaver.SetupContext() {
|
compoundWeaver.setup(weavingContextExtensionDataFactoryBuilder::registerPart);
|
||||||
@Override
|
|
||||||
public <E> RegisteredExtensionData<WeavingContext.ExtensionsData, E> registerWeavingContextExtensionData(Class<E> dataClass) {
|
PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build();
|
||||||
return (patchwork, extension) -> ((ExtensionsDataMock) patchwork).weavingConfig = (CompoundWeavingConfig) extension;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() {
|
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() {
|
||||||
@Override
|
@Override
|
||||||
@@ -46,7 +39,7 @@ class CompoundPojoWeaverTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, mock(ConfigContainer.class))
|
}, mock(ConfigContainer.class))
|
||||||
.extensionsData(new ExtensionsDataMock(null))
|
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||||
.annotations(Compound.class)
|
.annotations(Compound.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -86,39 +79,4 @@ class CompoundPojoWeaverTest {
|
|||||||
public static class InnerValue {
|
public static class InnerValue {
|
||||||
public Integer value;
|
public Integer value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
private static class ExtensionsDataMock implements WeavingContext.ExtensionsData, CompoundWeavingConfig {
|
|
||||||
private CompoundWeavingConfig weavingConfig;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPatchworkPartDefined(Class<?> patchworkInterface) {
|
|
||||||
return patchworkInterface == CompoundWeavingConfig.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPatchworkPartSet(Class<?> patchworkInterface) {
|
|
||||||
return weavingConfig != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WeavingContext.ExtensionsData copy() {
|
|
||||||
return new ExtensionsDataMock(weavingConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NamingFormat compoundSourceNamingFormat() {
|
|
||||||
return weavingConfig.compoundSourceNamingFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NamingFormat compoundTargetNamingFormat() {
|
|
||||||
return weavingConfig.compoundTargetNamingFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Class<? extends WeavableCompoundConfigEntry> compoundEntryClass() {
|
|
||||||
return weavingConfig.compoundEntryClass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user