[patchwork, core, extensions] Hugely simplify Patchworks
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
[versions]
|
||||
assertj = "3.26.3"
|
||||
asm = "9.7"
|
||||
autoservice = "1.1.1"
|
||||
java-main = "8"
|
||||
java-test = "21"
|
||||
@@ -16,8 +15,6 @@ lombok = { id = "io.freefair.lombok", version = "8.13.1" }
|
||||
|
||||
[libraries]
|
||||
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-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
||||
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package de.siphalor.tweed5.core.api.container;
|
||||
|
||||
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -16,7 +15,7 @@ import java.util.Optional;
|
||||
* @param <T> The class that the config tree represents
|
||||
* @see ConfigContainerSetupPhase
|
||||
*/
|
||||
public interface ConfigContainer<T> {
|
||||
public interface ConfigContainer<T extends @Nullable Object> {
|
||||
@SuppressWarnings("rawtypes")
|
||||
TweedConstructFactory<ConfigContainer> FACTORY = TweedConstructFactory.builder(ConfigContainer.class).build();
|
||||
|
||||
@@ -36,10 +35,9 @@ public interface ConfigContainer<T> {
|
||||
|
||||
void attachTree(ConfigEntry<T> rootEntry);
|
||||
|
||||
EntryExtensionsData createExtensionsData();
|
||||
Patchwork createExtensionsData();
|
||||
|
||||
void initialize();
|
||||
|
||||
ConfigEntry<T> rootEntry();
|
||||
Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class BaseConfigEntry<T> implements ConfigEntry<T> {
|
||||
private final ConfigContainer<?> container;
|
||||
private final Class<T> valueClass;
|
||||
private final EntryExtensionsData extensionsData;
|
||||
private final Patchwork extensionsData;
|
||||
|
||||
public BaseConfigEntry(ConfigContainer<?> container, Class<T> valueClass) {
|
||||
this.container = container;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package de.siphalor.tweed5.core.api.entry;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface CollectionConfigEntry<E, T extends Collection<E>> extends ConfigEntry<T> {
|
||||
@Override
|
||||
default CollectionConfigEntry<E, T> apply(Consumer<ConfigEntry<T>> function) {
|
||||
ConfigEntry.super.apply(function);
|
||||
return this;
|
||||
}
|
||||
|
||||
ConfigEntry<E> elementEntry();
|
||||
|
||||
T instantiateCollection(int size);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package de.siphalor.tweed5.core.api.entry;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface CompoundConfigEntry<T> extends ConfigEntry<T> {
|
||||
@Override
|
||||
default CompoundConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
|
||||
ConfigEntry.super.apply(function);
|
||||
return this;
|
||||
}
|
||||
|
||||
Map<String, ConfigEntry<?>> subEntries();
|
||||
|
||||
<V> void set(T compoundValue, String key, V value);
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
package de.siphalor.tweed5.core.api.entry;
|
||||
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public interface ConfigEntry<T> {
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface ConfigEntry<T extends @Nullable Object> {
|
||||
|
||||
ConfigContainer<?> container();
|
||||
|
||||
Class<T> valueClass();
|
||||
|
||||
EntryExtensionsData extensionsData();
|
||||
Patchwork extensionsData();
|
||||
|
||||
default ConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
|
||||
function.accept(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
default <R> R call(Function<ConfigEntry<T>, R> function) {
|
||||
return function.apply(this);
|
||||
}
|
||||
|
||||
void visitInOrder(ConfigEntryVisitor visitor);
|
||||
void visitInOrder(ConfigEntryValueVisitor visitor, T value);
|
||||
void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value);
|
||||
|
||||
T deepCopy(T value);
|
||||
T deepCopy(@NonNull T value);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
package de.siphalor.tweed5.core.api.entry;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface SimpleConfigEntry<T> extends ConfigEntry<T> {
|
||||
@Override
|
||||
default SimpleConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
|
||||
ConfigEntry.super.apply(function);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||
|
||||
public interface TweedExtensionSetupContext {
|
||||
<E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass);
|
||||
<E> PatchworkPartAccess<E> registerEntryExtensionData(Class<E> dataClass);
|
||||
void registerExtension(Class<? extends TweedExtension> extensionClass);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,15 @@ package de.siphalor.tweed5.core.impl;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||
import de.siphalor.tweed5.utils.api.collection.InheritanceMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
@@ -28,9 +22,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
private final Set<Class<? extends TweedExtension>> requestedExtensions = new HashSet<>();
|
||||
private final InheritanceMap<TweedExtension> extensions = new InheritanceMap<>(TweedExtension.class);
|
||||
private @Nullable ConfigEntry<T> rootEntry;
|
||||
private @Nullable PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
||||
private final Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions
|
||||
= new HashMap<>();
|
||||
private @Nullable PatchworkFactory entryExtensionsDataPatchworkFactory;
|
||||
|
||||
@Override
|
||||
public void registerExtension(Class<? extends TweedExtension> extensionClass) {
|
||||
@@ -42,15 +34,12 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
public void finishExtensionSetup() {
|
||||
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
||||
|
||||
PatchworkFactory.Builder entryExtensionDataPatchworkFactoryBuilder = PatchworkFactory.builder();
|
||||
|
||||
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
|
||||
@Override
|
||||
public <E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass) {
|
||||
if (registeredEntryDataExtensions.containsKey(dataClass)) {
|
||||
throw new IllegalArgumentException("Extension " + dataClass.getName() + " is already registered");
|
||||
}
|
||||
RegisteredExtensionDataImpl<EntryExtensionsData, E> registered = new RegisteredExtensionDataImpl<>();
|
||||
registeredEntryDataExtensions.put(dataClass, registered);
|
||||
return registered;
|
||||
public <E> PatchworkPartAccess<E> registerEntryExtensionData(Class<E> dataClass) {
|
||||
return entryExtensionDataPatchworkFactoryBuilder.registerPart(dataClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,20 +70,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
}
|
||||
}
|
||||
|
||||
PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()
|
||||
.patchworkInterface(EntryExtensionsData.class)
|
||||
.classPackage("de.siphalor.tweed5.core.generated.entryextensiondata")
|
||||
.classPrefix("EntryExtensionsData$")
|
||||
.build();
|
||||
try {
|
||||
entryExtensionsDataPatchworkClass = entryExtensionsDataGenerator.createClass(registeredEntryDataExtensions.keySet());
|
||||
for (PatchworkClassPart part : entryExtensionsDataPatchworkClass.parts()) {
|
||||
RegisteredExtensionDataImpl<EntryExtensionsData, ?> registeredExtension = registeredEntryDataExtensions.get(part.partInterface());
|
||||
registeredExtension.setter(part.fieldSetter());
|
||||
}
|
||||
} catch (PatchworkClassGenerator.GenerationException e) {
|
||||
throw new IllegalStateException("Failed to create patchwork class for entry extensions' data", e);
|
||||
}
|
||||
entryExtensionsDataPatchworkFactory = entryExtensionDataPatchworkFactoryBuilder.build();
|
||||
|
||||
setupPhase = ConfigContainerSetupPhase.TREE_SETUP;
|
||||
|
||||
@@ -230,25 +206,11 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntryExtensionsData createExtensionsData() {
|
||||
public Patchwork createExtensionsData() {
|
||||
requireSetupPhase(ConfigContainerSetupPhase.TREE_SETUP);
|
||||
|
||||
try {
|
||||
assert entryExtensionsDataPatchworkClass != null;
|
||||
return (EntryExtensionsData) entryExtensionsDataPatchworkClass.constructor().invoke();
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to construct patchwork class for entry extensions' data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Class<?>, ? extends RegisteredExtensionData<EntryExtensionsData, ?>> entryDataExtensions() {
|
||||
requireSetupPhase(
|
||||
ConfigContainerSetupPhase.TREE_SETUP,
|
||||
ConfigContainerSetupPhase.TREE_ATTACHED,
|
||||
ConfigContainerSetupPhase.INITIALIZED
|
||||
);
|
||||
return registeredEntryDataExtensions;
|
||||
assert entryExtensionsDataPatchworkFactory != null;
|
||||
return entryExtensionsDataPatchworkFactory.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,18 +257,4 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
private static class RegisteredExtensionDataImpl<U extends Patchwork<U>, E> implements RegisteredExtensionData<U, E> {
|
||||
private MethodHandle setter;
|
||||
|
||||
@Override
|
||||
public void set(U patchwork, E extension) {
|
||||
try {
|
||||
setter.invokeWithArguments(patchwork, extension);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements SimpleConfigEntry<T> {
|
||||
public SimpleConfigEntryImpl(ConfigContainer<?> container, Class<T> valueClass) {
|
||||
@@ -22,7 +23,7 @@ public class SimpleConfigEntryImpl<T> extends BaseConfigEntry<T> implements Simp
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deepCopy(T value) {
|
||||
public T deepCopy(@NonNull T value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package de.siphalor.tweed5.core.impl;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||
@@ -131,28 +129,7 @@ class DefaultConfigContainerTest {
|
||||
configContainer.registerExtension(ExtensionB.class);
|
||||
configContainer.finishExtensionSetup();
|
||||
var extensionData = configContainer.createExtensionsData();
|
||||
assertThat(extensionData).isNotNull()
|
||||
.satisfies(
|
||||
data -> assertThat(data.isPatchworkPartDefined(ExtensionBData.class)).isTrue(),
|
||||
data -> assertThat(data.isPatchworkPartDefined(String.class)).isFalse()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void entryDataExtensions() {
|
||||
var configContainer = new DefaultConfigContainer<>();
|
||||
configContainer.registerExtension(ExtensionB.class);
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
assertThat(configContainer.entryDataExtensions()).containsOnlyKeys(ExtensionBData.class);
|
||||
//noinspection unchecked
|
||||
var registeredExtension = (RegisteredExtensionData<EntryExtensionsData, ExtensionBData>)
|
||||
configContainer.entryDataExtensions().get(ExtensionBData.class);
|
||||
|
||||
var extensionsData = configContainer.createExtensionsData();
|
||||
registeredExtension.set(extensionsData, new ExtensionBDataImpl("blub"));
|
||||
|
||||
assertThat(((ExtensionBData) extensionsData).test()).isEqualTo("blub");
|
||||
assertThat(extensionData).isNotNull();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -5,8 +5,20 @@ import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface CommentExtension extends TweedExtension {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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 de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||
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.Value;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@AutoService(CommentExtension.class)
|
||||
public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension {
|
||||
private final ConfigContainer<?> configContainer;
|
||||
@Getter
|
||||
private final RegisteredExtensionData<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension;
|
||||
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||
private final DefaultMiddlewareContainer<CommentProducer> middlewareContainer;
|
||||
|
||||
public CommentExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
||||
this.configContainer = configContainer;
|
||||
this.internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class);
|
||||
context.registerEntryExtensionData(EntryComment.class);
|
||||
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||
this.middlewareContainer = new DefaultMiddlewareContainer<>();
|
||||
}
|
||||
|
||||
@@ -48,31 +48,49 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE
|
||||
|
||||
@Override
|
||||
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
|
||||
public void initEntry(ConfigEntry<?> configEntry) {
|
||||
EntryExtensionsData entryExtensionsData = configEntry.extensionsData();
|
||||
String baseComment;
|
||||
if (entryExtensionsData.isPatchworkPartSet(EntryComment.class)) {
|
||||
baseComment = ((EntryComment) entryExtensionsData).comment();
|
||||
} else {
|
||||
baseComment = "";
|
||||
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
|
||||
entryData.commentProducer(middlewareContainer.process(entry -> entryData.baseComment()));
|
||||
}
|
||||
|
||||
CommentProducer middleware = middlewareContainer.process(entry -> baseComment);
|
||||
internalEntryDataExtension.set(entryExtensionsData, new InternalCommentEntryDataImpl(middleware));
|
||||
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
|
||||
public @Nullable String getFullComment(@NonNull ConfigEntry<?> configEntry) {
|
||||
String comment = ((InternalCommentEntryData) configEntry.extensionsData()).commentProducer().createComment(configEntry);
|
||||
public @Nullable String getFullComment(ConfigEntry<?> configEntry) {
|
||||
CustomEntryData customEntryData = configEntry.extensionsData().get(customEntryDataAccess);
|
||||
if (customEntryData == null) {
|
||||
return null;
|
||||
}
|
||||
String comment = customEntryData.commentProducer().createComment(configEntry);
|
||||
return comment.isEmpty() ? null : comment;
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class InternalCommentEntryDataImpl implements InternalCommentEntryData {
|
||||
CommentProducer commentProducer;
|
||||
|
||||
@Data
|
||||
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.data.extension.api.TweedEntryWriter;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?, ?>> {
|
||||
public static final TweedEntryWriterCommentMiddleware INSTANCE = new TweedEntryWriterCommentMiddleware();
|
||||
private final CommentExtension commentExtension;
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
@@ -49,7 +51,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class CompoundDataVisitor implements TweedDataVisitor {
|
||||
private class CompoundDataVisitor implements TweedDataVisitor {
|
||||
private final TweedDataVisitor delegate;
|
||||
private final CompoundConfigEntry<?> compoundConfigEntry;
|
||||
|
||||
@@ -134,11 +136,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getEntryComment(ConfigEntry<?> entry) {
|
||||
if (!entry.extensionsData().isPatchworkPartSet(InternalCommentEntryData.class)) {
|
||||
return null;
|
||||
}
|
||||
String comment = ((InternalCommentEntryData) entry.extensionsData()).commentProducer().createComment(entry).trim();
|
||||
return comment.isEmpty() ? null : comment;
|
||||
private @Nullable String getEntryComment(ConfigEntry<?> entry) {
|
||||
return commentExtension.getFullComment(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
public class PathTracking implements PatherData {
|
||||
public class PathTracking {
|
||||
private final StringBuilder pathBuilder = new StringBuilder(256);
|
||||
private final Deque<Context> contextStack = new ArrayDeque<>(50);
|
||||
private final Deque<String> pathParts = new ArrayDeque<>(50);
|
||||
@@ -52,8 +52,7 @@ public class PathTracking implements PatherData {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String valuePath() {
|
||||
public String currentPath() {
|
||||
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.ConfigEntryValueVisitor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor {
|
||||
@@ -10,7 +11,7 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
|
||||
private final PathTracking pathTracking;
|
||||
|
||||
@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);
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
public interface PatherExtension extends TweedExtension {
|
||||
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 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.middleware.Middleware;
|
||||
import de.siphalor.tweed5.data.extension.api.*;
|
||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||
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.ReadWriteRelatedExtension;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||
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 org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@AutoService(PatherExtension.class)
|
||||
@NullUnmarked
|
||||
public class PatherExtensionImpl implements PatherExtension, TweedExtension, ReadWriteRelatedExtension {
|
||||
private static final String PATHER_ID = "pather";
|
||||
|
||||
private RegisteredExtensionData<ReadWriteContextExtensionsData, PatherData> rwContextPathTrackingData;
|
||||
private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware;
|
||||
private Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware;
|
||||
private final Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware = createEntryReaderMiddleware();
|
||||
private final Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware = createEntryWriterMiddleware();
|
||||
|
||||
private @Nullable PatchworkPartAccess<PathTracking> rwContextPathTrackingAccess;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
@@ -33,13 +37,32 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
|
||||
|
||||
@Override
|
||||
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
||||
rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PatherData.class);
|
||||
|
||||
entryReaderMiddleware = createEntryReaderMiddleware();
|
||||
entryWriterMiddleware = createEntryWriterMiddleware();
|
||||
rwContextPathTrackingAccess = context.registerReadWriteContextExtensionData(PathTracking.class);
|
||||
}
|
||||
|
||||
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<?, ?>>() {
|
||||
@Override
|
||||
public String id() {
|
||||
@@ -48,16 +71,19 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
|
||||
|
||||
@Override
|
||||
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
||||
assert rwContextPathTrackingAccess != null;
|
||||
|
||||
//noinspection unchecked
|
||||
val castedInner = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
PathTracking pathTracking = new PathTracking();
|
||||
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
|
||||
pathTracking = new PathTracking();
|
||||
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
|
||||
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
|
||||
};
|
||||
}
|
||||
@@ -73,17 +99,20 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
|
||||
|
||||
@Override
|
||||
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
|
||||
assert rwContextPathTrackingAccess != null;
|
||||
|
||||
//noinspection unchecked
|
||||
val castedInner = (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
PathTracking pathTracking = new PathTracking();
|
||||
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
|
||||
pathTracking = new PathTracking();
|
||||
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
|
||||
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.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.validators.SimpleValidatorMiddleware;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface ValidationExtension extends TweedExtension {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.validation.api.result;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -11,16 +12,16 @@ import java.util.function.Function;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ValidationResult<T> {
|
||||
public class ValidationResult<T extends @Nullable Object> {
|
||||
private final T value;
|
||||
private final Collection<ValidationIssue> issues;
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.entry.ConfigEntry;
|
||||
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.TweedExtensionSetupContext;
|
||||
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.TweedEntryReader;
|
||||
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.ReadWriteRelatedExtension;
|
||||
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.pather.api.PathTracking;
|
||||
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor;
|
||||
import de.siphalor.tweed5.defaultextensions.pather.api.PatherData;
|
||||
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||
import lombok.*;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
@@ -48,7 +41,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
);
|
||||
private static final ConfigEntryValidator PRIMITIVE_VALIDATOR = new ConfigEntryValidator() {
|
||||
@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) {
|
||||
//noinspection unchecked
|
||||
return (ValidationResult<T>) PRIMITIVE_IS_NULL_RESULT;
|
||||
@@ -57,33 +50,32 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> String description(@NotNull ConfigEntry<T> configEntry) {
|
||||
public <T> String description(ConfigEntry<T> configEntry) {
|
||||
return "Value must not be null.";
|
||||
}
|
||||
};
|
||||
private static final ConfigEntryValidator NOOP_VALIDATOR = new ConfigEntryValidator() {
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> String description(@NotNull ConfigEntry<T> configEntry) {
|
||||
public <T> String description(ConfigEntry<T> configEntry) {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
private final ConfigContainer<?> configContainer;
|
||||
private final RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
|
||||
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||
private final MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer
|
||||
= new DefaultMiddlewareContainer<>();
|
||||
private @Nullable RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues>
|
||||
readContextValidationIssuesExtensionData;
|
||||
private @Nullable PatchworkPartAccess<ValidationIssues> readContextValidationIssuesAccess;
|
||||
private @Nullable PatherExtension patherExtension;
|
||||
|
||||
public ValidationExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
||||
this.configContainer = configContainer;
|
||||
this.validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class);
|
||||
context.registerEntryExtensionData(EntrySpecificValidation.class);
|
||||
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||
context.registerExtension(PatherExtension.class);
|
||||
}
|
||||
|
||||
@@ -102,6 +94,9 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
}
|
||||
}
|
||||
entryValidatorMiddlewareContainer.seal();
|
||||
|
||||
patherExtension = configContainer.extension(PatherExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("Missing requested PatherExtension"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,16 +111,21 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
public CommentProducer process(CommentProducer inner) {
|
||||
return entry -> {
|
||||
String baseComment = inner.createComment(entry);
|
||||
if (entry.extensionsData().isPatchworkPartSet(InternalValidationEntryData.class)) {
|
||||
String validationDescription = ((InternalValidationEntryData) entry.extensionsData())
|
||||
.completeEntryValidator()
|
||||
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||
if (entryData == null || entryData.completeValidator() == null) {
|
||||
return baseComment;
|
||||
}
|
||||
String validationDescription = entryData.completeValidator()
|
||||
.description(entry)
|
||||
.trim();
|
||||
if (!validationDescription.isEmpty()) {
|
||||
baseComment += "\n\n" + validationDescription;
|
||||
}
|
||||
}
|
||||
if (validationDescription.isEmpty()) {
|
||||
return baseComment;
|
||||
}
|
||||
if (baseComment.isEmpty()) {
|
||||
return validationDescription;
|
||||
} else {
|
||||
return "\n\n" + validationDescription;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -133,7 +133,13 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
|
||||
@Override
|
||||
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
|
||||
@@ -145,26 +151,26 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
baseValidator = NOOP_VALIDATOR;
|
||||
}
|
||||
|
||||
ConfigEntryValidator entryValidator;
|
||||
Collection<Middleware<ConfigEntryValidator>> entrySpecificValidators = getEntrySpecificValidators(configEntry);
|
||||
if (entrySpecificValidators.isEmpty()) {
|
||||
entryValidator = entryValidatorMiddlewareContainer.process(baseValidator);
|
||||
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
|
||||
|
||||
if (entryData.validators().isEmpty()) {
|
||||
entryData.completeValidator(entryValidatorMiddlewareContainer.process(baseValidator));
|
||||
} else {
|
||||
DefaultMiddlewareContainer<ConfigEntryValidator> entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>();
|
||||
entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares());
|
||||
entrySpecificValidatorContainer.registerAll(entrySpecificValidators);
|
||||
entrySpecificValidatorContainer.registerAll(entryData.validators());
|
||||
entrySpecificValidatorContainer.seal();
|
||||
entryValidator = entrySpecificValidatorContainer.process(baseValidator);
|
||||
entryData.completeValidator(entrySpecificValidatorContainer.process(baseValidator));
|
||||
}
|
||||
}
|
||||
|
||||
validationEntryDataExtension.set(configEntry.extensionsData(), new InternalValidationEntryDataImpl(entryValidator));
|
||||
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
|
||||
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||
if (entryData == null) {
|
||||
entryData = new CustomEntryData();
|
||||
entry.extensionsData().set(customEntryDataAccess, entryData);
|
||||
}
|
||||
|
||||
private Collection<Middleware<ConfigEntryValidator>> getEntrySpecificValidators(ConfigEntry<?> configEntry) {
|
||||
if (!configEntry.extensionsData().isPatchworkPartSet(EntrySpecificValidation.class)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return ((EntrySpecificValidation) configEntry.extensionsData()).validators();
|
||||
return entryData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,7 +179,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
}
|
||||
|
||||
@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();
|
||||
ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking);
|
||||
|
||||
@@ -182,9 +188,22 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
return validatingVisitor.validationIssues();
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class InternalValidationEntryDataImpl implements InternalValidationEntryData {
|
||||
ConfigEntryValidator completeEntryValidator;
|
||||
@Data
|
||||
private static class CustomEntryData {
|
||||
@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<?, ?>> {
|
||||
@@ -200,23 +219,29 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
|
||||
@Override
|
||||
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
||||
assert readContextValidationIssuesAccess != null && patherExtension != null;
|
||||
|
||||
//noinspection unchecked
|
||||
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
|
||||
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
|
||||
ValidationIssues validationIssues;
|
||||
if (!context.extensionsData().isPatchworkPartSet(ValidationIssues.class)) {
|
||||
ValidationIssues validationIssues = context.extensionsData().get(readContextValidationIssuesAccess);
|
||||
if (validationIssues == null) {
|
||||
validationIssues = new ValidationIssuesImpl();
|
||||
readContextValidationIssuesExtensionData.set(context.extensionsData(), validationIssues);
|
||||
} else {
|
||||
validationIssues = (ValidationIssues) context.extensionsData();
|
||||
}
|
||||
|
||||
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)) {
|
||||
String path = ((PatherData) context.extensionsData()).valuePath();
|
||||
ValidationResult<Object> validationResult = entryValidator.validate(entry, value);
|
||||
|
||||
if (!validationResult.issues().isEmpty()) {
|
||||
String path = patherExtension.getPath(context);
|
||||
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
|
||||
entry,
|
||||
validationResult.issues()
|
||||
@@ -234,15 +259,19 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
private static class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor {
|
||||
private class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor {
|
||||
private final PathTracking pathTracking;
|
||||
private final ValidationIssues validationIssues = new ValidationIssuesImpl();
|
||||
|
||||
@Override
|
||||
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()) {
|
||||
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;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface ValidationFallbackExtension extends TweedExtension {
|
||||
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.ValidationResult;
|
||||
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 java.util.ArrayList;
|
||||
@@ -20,8 +21,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
@AutoService(ValidationFallbackExtension.class)
|
||||
public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension {
|
||||
|
||||
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||
|
||||
public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) {
|
||||
context.registerEntryExtensionData(ValidationFallbackValue.class);
|
||||
customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,12 +33,31 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
|
||||
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
|
||||
public Middleware<ConfigEntryValidator> validationMiddleware() {
|
||||
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
|
||||
public String id() {
|
||||
return "validation-fallback";
|
||||
@@ -59,13 +82,14 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
|
||||
if (!result.hasError()) {
|
||||
return result;
|
||||
}
|
||||
if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) {
|
||||
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
|
||||
if (entryData == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Object fallbackValue = ((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue();
|
||||
Object fallbackValue = entryData.fallbackValue();
|
||||
if (fallbackValue != null) {
|
||||
if (fallbackValue.getClass() == configEntry.valueClass()) {
|
||||
if (configEntry.valueClass().isInstance(fallbackValue)) {
|
||||
//noinspection unchecked
|
||||
fallbackValue = configEntry.deepCopy((T) fallbackValue);
|
||||
} else {
|
||||
@@ -90,12 +114,11 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
|
||||
|
||||
@Override
|
||||
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) +
|
||||
"\n\nDefault/Fallback value: " +
|
||||
((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue();
|
||||
return inner.description(configEntry) + "\n\nDefault/Fallback value: " + entryData.fallbackValue();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
package de.siphalor.tweed5.defaultextensions.comment.impl;
|
||||
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
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.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||
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.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.HjsonWriter;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -31,6 +24,9 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
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 java.util.Map.entry;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -38,44 +34,44 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
@NullUnmarked
|
||||
class CommentExtensionImplTest {
|
||||
|
||||
private DefaultConfigContainer<Map<String, Object>> configContainer;
|
||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
||||
private SimpleConfigEntryImpl<String> stringEntry;
|
||||
private SimpleConfigEntryImpl<Long> noCommentEntry;
|
||||
private DefaultConfigContainer<@NonNull Map<String, Object>> configContainer;
|
||||
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntry<Integer> intEntry;
|
||||
private SimpleConfigEntry<String> stringEntry;
|
||||
private SimpleConfigEntry<Long> noCommentEntry;
|
||||
|
||||
@SafeVarargs
|
||||
final void setupContainer(Class<? extends TweedExtension>... extraExtensions) {
|
||||
configContainer = new DefaultConfigContainer<>();
|
||||
|
||||
configContainer.registerExtension(CommentExtension.DEFAULT);
|
||||
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
|
||||
configContainer.registerExtensions(extraExtensions);
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
||||
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class);
|
||||
noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class);
|
||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()))
|
||||
.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
|
||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||
configContainer,
|
||||
((Class<Map<String, Object>>)(Class<?>) Map.class),
|
||||
((Class<Map<String, Object>>) (Class<?>) Map.class),
|
||||
LinkedHashMap::new,
|
||||
sequencedMap(List.of(
|
||||
entry("int", intEntry),
|
||||
entry("string", stringEntry),
|
||||
entry("noComment", noCommentEntry)
|
||||
))
|
||||
);
|
||||
)))
|
||||
.apply(entryReaderWriter(compoundReaderWriter()))
|
||||
.apply(baseComment("This is the root value.\nIt is the topmost value in the tree."));
|
||||
|
||||
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
|
||||
@@ -102,8 +98,7 @@ class CommentExtensionImplTest {
|
||||
|
||||
@Test
|
||||
void simpleCommentsInHjson() {
|
||||
setupContainer(ReadWriteExtension.DEFAULT);
|
||||
setupReadWriteTypes();
|
||||
setupContainer();
|
||||
configContainer.initialize();
|
||||
|
||||
Map<String, Object> value = new HashMap<>();
|
||||
@@ -134,21 +129,6 @@ class CommentExtensionImplTest {
|
||||
""", 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
|
||||
public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension {
|
||||
@Override
|
||||
@@ -158,7 +138,7 @@ class CommentExtensionImplTest {
|
||||
|
||||
@Override
|
||||
public Middleware<CommentProducer> commentMiddleware() {
|
||||
return new Middleware<CommentProducer>() {
|
||||
return new Middleware<>() {
|
||||
@Override
|
||||
public String id() {
|
||||
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;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||
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.entry.SimpleConfigEntry;
|
||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
|
||||
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.ValidationIssueLevel;
|
||||
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.SimpleValidatorMiddleware;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
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 java.util.Map.entry;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ValidationExtensionImplTest {
|
||||
private DefaultConfigContainer<Map<String, Object>> configContainer;
|
||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntryImpl<Byte> byteEntry;
|
||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
||||
private SimpleConfigEntryImpl<Double> doubleEntry;
|
||||
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||
private SimpleConfigEntry<Byte> byteEntry;
|
||||
private SimpleConfigEntry<Integer> intEntry;
|
||||
private SimpleConfigEntry<Double> doubleEntry;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -41,9 +43,13 @@ class ValidationExtensionImplTest {
|
||||
configContainer.registerExtension(ValidationExtension.DEFAULT);
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class);
|
||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
||||
doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class);
|
||||
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class)
|
||||
.apply(validators(new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100)));
|
||||
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
|
||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||
@@ -59,25 +65,6 @@ class ValidationExtensionImplTest {
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
package de.siphalor.tweed5.defaultextensions.validationfallback.impl;
|
||||
|
||||
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.entry.SimpleConfigEntry;
|
||||
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||
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.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.HjsonLexer;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonReader;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonWriter;
|
||||
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.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.result.ValidationIssue;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
|
||||
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
|
||||
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -33,18 +24,19 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
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.assertEquals;
|
||||
|
||||
class ValidationFallbackExtensionImplTest {
|
||||
private DefaultConfigContainer<Integer> configContainer;
|
||||
private SimpleConfigEntryImpl<Integer> intEntry;
|
||||
private SimpleConfigEntry<Integer> intEntry;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
configContainer = new DefaultConfigContainer<>();
|
||||
@@ -55,92 +47,82 @@ class ValidationFallbackExtensionImplTest {
|
||||
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
||||
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() {
|
||||
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(nullableReader(intReaderWriter()), nullableWriter(intReaderWriter())))
|
||||
.apply(validators(
|
||||
new ConfigEntryValidator() {
|
||||
@Override
|
||||
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
||||
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.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) {
|
||||
public <T> String description(ConfigEntry<T> configEntry) {
|
||||
return "Must not be null.";
|
||||
}
|
||||
}),
|
||||
new SimpleValidatorMiddleware("range", new ConfigEntryValidator() {
|
||||
},
|
||||
new ConfigEntryValidator() {
|
||||
|
||||
@Override
|
||||
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
|
||||
Integer intValue = (Integer) 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)));
|
||||
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.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) {
|
||||
public <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());
|
||||
}
|
||||
});
|
||||
))
|
||||
.apply(validationFallbackValue(3));
|
||||
|
||||
configContainer.attachTree(intEntry);
|
||||
configContainer.initialize();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"0", "7", "123", "null"})
|
||||
void fallbackTriggers(String input) {
|
||||
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
|
||||
Integer result = assertDoesNotThrow(() -> readWriteExtension.read(
|
||||
new HjsonReader(new HjsonLexer(new StringReader(input))),
|
||||
intEntry,
|
||||
readWriteExtension.createReadWriteContextExtensionsData()
|
||||
));
|
||||
Integer result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input)))));
|
||||
assertEquals(3, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void description() {
|
||||
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
assertDoesNotThrow(() -> readWriteExtension.write(
|
||||
intEntry.apply(write(
|
||||
new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)),
|
||||
5,
|
||||
intEntry,
|
||||
readWriteExtension.createReadWriteContextExtensionsData()
|
||||
5
|
||||
));
|
||||
|
||||
assertEquals(
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
plugins {
|
||||
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;
|
||||
|
||||
public interface Patchwork<S extends Patchwork<S>> {
|
||||
boolean isPatchworkPartDefined(Class<?> patchworkInterface);
|
||||
boolean isPatchworkPartSet(Class<?> patchworkInterface);
|
||||
import org.jetbrains.annotations.CheckReturnValue;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
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.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.dataapi.api.TweedDataReader;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface ReadWriteExtension extends TweedExtension {
|
||||
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,
|
||||
@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,
|
||||
ReadWriteContextExtensionsData contextExtensionsData
|
||||
) throws TweedEntryReadException;
|
||||
TweedEntryReader<T, C> entryReader,
|
||||
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(
|
||||
TweedDataVisitor writer,
|
||||
T value,
|
||||
ConfigEntry<T> entry,
|
||||
ReadWriteContextExtensionsData contextExtensionsData
|
||||
Patchwork contextExtensionsData
|
||||
) 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;
|
||||
|
||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
|
||||
public interface TweedReadContext {
|
||||
ReadWriteContextExtensionsData extensionsData();
|
||||
ReadWriteExtension readWriteExtension();
|
||||
Patchwork extensionsData();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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 {
|
||||
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;
|
||||
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||
|
||||
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.data.extension.api.TweedEntryReader;
|
||||
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 de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
|
||||
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
|
||||
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.ReadWriteRelatedExtension;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
||||
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.TweedDataWriteException;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
||||
import lombok.Data;
|
||||
import lombok.val;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@AutoService(ReadWriteExtension.class)
|
||||
public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
private final ConfigContainer<?> configContainer;
|
||||
private final RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>
|
||||
readerWriterDefinitionExtension;
|
||||
private final RegisteredExtensionData<EntryExtensionsData, ReadWriteEntryDataExtension> readWriteEntryDataExtension;
|
||||
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
|
||||
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>>
|
||||
entryReaderMiddlewareContainer
|
||||
= new DefaultMiddlewareContainer<>();
|
||||
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>>
|
||||
entryWriterMiddlewareContainer
|
||||
= new DefaultMiddlewareContainer<>();
|
||||
private final Map<Class<?>, RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>>
|
||||
readWriteContextExtensionsDataClasses
|
||||
= new HashMap<>();
|
||||
private @Nullable PatchworkClass<@NonNull ReadWriteContextExtensionsData> readWriteContextExtensionsDataPatchwork;
|
||||
private @Nullable PatchworkFactory readWriteContextPatchworkFactory;
|
||||
|
||||
public ReadWriteExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
|
||||
this.configContainer = configContainer;
|
||||
this.readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class);
|
||||
this.readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class);
|
||||
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,23 +49,8 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
public void extensionsFinalized() {
|
||||
Collection<TweedExtension> extensions = configContainer.extensions();
|
||||
|
||||
ReadWriteExtensionSetupContext setupContext = new ReadWriteExtensionSetupContext() {
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
};
|
||||
PatchworkFactory.Builder readWriteContextPatchworkFactorBuilder = PatchworkFactory.builder();
|
||||
ReadWriteExtensionSetupContext setupContext = readWriteContextPatchworkFactorBuilder::registerPart;
|
||||
|
||||
entryReaderMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
||||
entryWriterMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
||||
@@ -102,79 +72,62 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
}
|
||||
}
|
||||
|
||||
readWriteContextPatchworkFactory = readWriteContextPatchworkFactorBuilder.build();
|
||||
|
||||
entryReaderMiddlewareContainer.seal();
|
||||
entryWriterMiddlewareContainer.seal();
|
||||
|
||||
val patchworkClassCreator = PatchworkClassCreator.<ReadWriteContextExtensionsData>builder()
|
||||
.patchworkInterface(ReadWriteContextExtensionsData.class)
|
||||
.classPackage("de.siphalor.tweed5.data.extension.generated")
|
||||
.classPrefix("ReadWriteContextExtensionsData$")
|
||||
.build();
|
||||
|
||||
try {
|
||||
readWriteContextExtensionsDataPatchwork =
|
||||
patchworkClassCreator.createClass(readWriteContextExtensionsDataClasses.keySet());
|
||||
for (PatchworkClassPart patchworkClassPart : readWriteContextExtensionsDataPatchwork.parts()) {
|
||||
RegisteredExtensionDataImpl<ReadWriteContextExtensionsData, ?>
|
||||
registeredExtension
|
||||
= readWriteContextExtensionsDataClasses.get(patchworkClassPart.partInterface());
|
||||
registeredExtension.setter = patchworkClassPart.fieldSetter();
|
||||
}
|
||||
} catch (PatchworkClassGenerator.GenerationException e) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to generate read write context extensions' data patchwork class",
|
||||
e
|
||||
);
|
||||
|
||||
@Override
|
||||
public <T, C extends ConfigEntry<T>> void setEntryReaderWriter(
|
||||
ConfigEntry<T> entry,
|
||||
TweedEntryReader<T, C> entryReader,
|
||||
TweedEntryWriter<T, C> entryWriter
|
||||
) {
|
||||
CustomEntryData customEntryData = getOrCreateCustomEntryData(entry);
|
||||
customEntryData.readerDefinition(entryReader);
|
||||
customEntryData.writerDefinition(entryWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, C extends ConfigEntry<T>> void setEntryReader(ConfigEntry<T> entry, TweedEntryReader<T, C> entryReader) {
|
||||
getOrCreateCustomEntryData(entry).readerDefinition(entryReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, C extends ConfigEntry<T>> void setEntryWriter(ConfigEntry<T> entry, TweedEntryWriter<T, C> entryWriter) {
|
||||
getOrCreateCustomEntryData(entry).writerDefinition(entryWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initEntry(ConfigEntry<?> configEntry) {
|
||||
TweedEntryReader<?, ?> baseReader;
|
||||
TweedEntryWriter<?, ?> baseWriter;
|
||||
if (configEntry.extensionsData().isPatchworkPartSet(EntryReaderWriterDefinition.class)) {
|
||||
EntryReaderWriterDefinition rwDefintion = (EntryReaderWriterDefinition) configEntry.extensionsData();
|
||||
baseReader = rwDefintion.reader();
|
||||
baseWriter = rwDefintion.writer();
|
||||
} else {
|
||||
baseReader = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
||||
baseWriter = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
||||
CustomEntryData customEntryData = getOrCreateCustomEntryData(configEntry);
|
||||
customEntryData.readerChain(entryReaderMiddlewareContainer.process(customEntryData.readerDefinition()));
|
||||
customEntryData.writerChain(entryWriterMiddlewareContainer.process(customEntryData.writerDefinition()));
|
||||
}
|
||||
|
||||
readWriteEntryDataExtension.set(
|
||||
configEntry.extensionsData(), new ReadWriteEntryDataExtensionImpl(
|
||||
entryReaderMiddlewareContainer.process(baseReader),
|
||||
entryWriterMiddlewareContainer.process(baseWriter)
|
||||
)
|
||||
);
|
||||
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
|
||||
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
|
||||
if (entryData == null) {
|
||||
entryData = new CustomEntryData();
|
||||
entry.extensionsData().set(customEntryDataAccess, entryData);
|
||||
}
|
||||
return entryData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryReaderWriterDefinition(
|
||||
@NonNull ConfigEntry<?> entry,
|
||||
@NonNull EntryReaderWriterDefinition readerWriterDefinition
|
||||
) {
|
||||
readerWriterDefinitionExtension.set(entry.extensionsData(), readerWriterDefinition);
|
||||
public Patchwork createReadWriteContextExtensionsData() {
|
||||
assert readWriteContextPatchworkFactory != null;
|
||||
return readWriteContextPatchworkFactory.create();
|
||||
}
|
||||
|
||||
@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(
|
||||
@NonNull TweedDataReader reader,
|
||||
@NonNull ConfigEntry<T> entry,
|
||||
@NonNull ReadWriteContextExtensionsData contextExtensionsData
|
||||
TweedDataReader reader,
|
||||
ConfigEntry<T> entry,
|
||||
Patchwork contextExtensionsData
|
||||
) throws TweedEntryReadException {
|
||||
try {
|
||||
return getReaderChain(entry).read(reader, entry, new TweedReadWriteContextImpl(contextExtensionsData));
|
||||
return getReaderChain(entry).read(reader, entry, new TweedReadWriteContextImpl(this, contextExtensionsData));
|
||||
} catch (TweedDataReadException e) {
|
||||
throw new TweedEntryReadException("Failed to read entry", e);
|
||||
}
|
||||
@@ -182,46 +135,35 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
|
||||
@Override
|
||||
public <T extends @Nullable Object> void write(
|
||||
@NonNull TweedDataVisitor writer,
|
||||
TweedDataVisitor writer,
|
||||
@Nullable T value,
|
||||
@NonNull ConfigEntry<T> entry,
|
||||
@NonNull ReadWriteContextExtensionsData contextExtensionsData
|
||||
ConfigEntry<T> entry,
|
||||
Patchwork contextExtensionsData
|
||||
) throws TweedEntryWriteException {
|
||||
try {
|
||||
getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(contextExtensionsData));
|
||||
getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(this, contextExtensionsData));
|
||||
} catch (TweedDataWriteException e) {
|
||||
throw new TweedEntryWriteException("Failed to write entry", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class ReadWriteEntryDataExtensionImpl implements ReadWriteEntryDataExtension {
|
||||
TweedEntryReader<?, ?> entryReaderChain;
|
||||
TweedEntryWriter<?, ?> entryWriterChain;
|
||||
@Data
|
||||
private static class CustomEntryData {
|
||||
private TweedEntryReader<?, ?> readerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
||||
private TweedEntryWriter<?, ?> writerDefinition = TweedEntryReaderWriterImpls.NOOP_READER_WRITER;
|
||||
private TweedEntryReader<?, ?> readerChain;
|
||||
private TweedEntryWriter<?, ?> writerChain;
|
||||
}
|
||||
|
||||
@Setter
|
||||
private static class RegisteredExtensionDataImpl<U extends Patchwork<@NonNull 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);
|
||||
}
|
||||
}
|
||||
public <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> getReaderChain(C entry) {
|
||||
//noinspection unchecked
|
||||
return (TweedEntryReader<T, C>) entry.extensionsData().get(customEntryDataAccess).readerChain();
|
||||
}
|
||||
|
||||
static <T> TweedEntryReader<T, @NonNull ConfigEntry<T>> getReaderChain(ConfigEntry<T> elementEntry) {
|
||||
@Override
|
||||
public <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> getWriterChain(C entry) {
|
||||
//noinspection unchecked
|
||||
return (TweedEntryReader<T, @NonNull ConfigEntry<T>>) ((ReadWriteEntryDataExtension) elementEntry.extensionsData()).entryReaderChain();
|
||||
}
|
||||
|
||||
static <T> TweedEntryWriter<T, @NonNull ConfigEntry<T>> getWriterChain(ConfigEntry<T> elementEntry) {
|
||||
//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.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
@@ -63,7 +62,7 @@ public class TweedEntryReaderWriterImpls {
|
||||
}
|
||||
|
||||
@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 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
|
||||
public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
||||
assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start");
|
||||
@@ -89,9 +88,9 @@ public class TweedEntryReaderWriterImpls {
|
||||
}
|
||||
|
||||
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) {
|
||||
token = reader.peekToken();
|
||||
if (token.isListEnd()) {
|
||||
@@ -119,7 +118,7 @@ public class TweedEntryReaderWriterImpls {
|
||||
}
|
||||
|
||||
ConfigEntry<T> elementEntry = entry.elementEntry();
|
||||
TweedEntryWriter<T, ConfigEntry<T>> elementWriter = ReadWriteExtensionImpl.getWriterChain(elementEntry);
|
||||
TweedEntryWriter<T, ConfigEntry<T>> elementWriter = context.readWriteExtension().getWriterChain(elementEntry);
|
||||
|
||||
writer.visitListStart();
|
||||
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
|
||||
public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
||||
assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start");
|
||||
@@ -145,7 +144,7 @@ public class TweedEntryReaderWriterImpls {
|
||||
|
||||
//noinspection unchecked
|
||||
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);
|
||||
entry.set(compoundValue, key, subEntryValue);
|
||||
} else {
|
||||
@@ -167,7 +166,7 @@ public class TweedEntryReaderWriterImpls {
|
||||
String key = e.getKey();
|
||||
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);
|
||||
subEntryWriterChain.write(writer, entry.get(value, key), subEntry, context);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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.TweedWriteContext;
|
||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class TweedReadWriteContextImpl implements TweedReadContext, TweedWriteContext {
|
||||
ReadWriteContextExtensionsData extensionsData;
|
||||
ReadWriteExtension readWriteExtension;
|
||||
Patchwork extensionsData;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
package de.siphalor.tweed5.data.extension.impl;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.api.entry.CollectionConfigEntry;
|
||||
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.entry.CollectionConfigEntryImpl;
|
||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||
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.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.HjsonReader;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonWriter;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -27,6 +22,8 @@ import java.io.Writer;
|
||||
import java.util.*;
|
||||
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 java.util.Map.entry;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -36,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
class ReadWriteExtensionImplTest {
|
||||
private final StringWriter stringWriter = new StringWriter();
|
||||
private final ConfigContainer<Map<String, Object>> configContainer = new DefaultConfigContainer<>();
|
||||
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||
private CompoundConfigEntry<Map<String, Object>> rootEntry;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
@@ -44,14 +41,16 @@ class ReadWriteExtensionImplTest {
|
||||
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
SimpleConfigEntryImpl<Integer> intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
|
||||
SimpleConfigEntryImpl<Boolean> booleanEntry = new SimpleConfigEntryImpl<>(configContainer, Boolean.class);
|
||||
CollectionConfigEntryImpl<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>(
|
||||
SimpleConfigEntry<Integer> intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
|
||||
.apply(entryReaderWriter(intReaderWriter()));
|
||||
|
||||
CollectionConfigEntry<Boolean, List<Boolean>> listEntry = new CollectionConfigEntryImpl<>(
|
||||
configContainer,
|
||||
(Class<List<Boolean>>) (Class<?>) List.class,
|
||||
ArrayList::new,
|
||||
booleanEntry
|
||||
);
|
||||
new SimpleConfigEntryImpl<>(configContainer, Boolean.class)
|
||||
.apply(entryReaderWriter(booleanReaderWriter()))
|
||||
).apply(entryReaderWriter(collectionReaderWriter()));
|
||||
|
||||
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
|
||||
configContainer,
|
||||
@@ -61,16 +60,9 @@ class ReadWriteExtensionImplTest {
|
||||
entry("int", intEntry),
|
||||
entry("list", listEntry)
|
||||
))
|
||||
);
|
||||
).apply(entryReaderWriter(compoundReaderWriter()));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -128,19 +120,4 @@ class ReadWriteExtensionImplTest {
|
||||
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {
|
||||
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;
|
||||
}
|
||||
|
||||
EntryReaderWriterDefinition definition = createDefinitionFromEntryConfig(entryConfig, context);
|
||||
if (definition != null) {
|
||||
readWriteExtension.setEntryReaderWriterDefinition(configEntry, definition);
|
||||
}
|
||||
//noinspection rawtypes,unchecked
|
||||
readWriteExtension.setEntryReaderWriter(
|
||||
(ConfigEntry) configEntry,
|
||||
(TweedEntryReader) resolveReader(entryConfig, context),
|
||||
(TweedEntryWriter) resolveWriter(entryConfig, context)
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable EntryReaderWriterDefinition createDefinitionFromEntryConfig(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
||||
String readerSpecText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
||||
String writerSpecText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer();
|
||||
|
||||
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;
|
||||
}
|
||||
private TweedEntryReader<?, ?> resolveReader(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
||||
String specText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
||||
SerdePojoReaderWriterSpec spec = specFromText(specText, context);
|
||||
|
||||
//noinspection unchecked,rawtypes
|
||||
TweedEntryReader<?, ?> reader = Optional.ofNullable(readerSpec)
|
||||
.map((spec) -> resolveReaderWriterFromSpec((Class<TweedEntryReader<?, ?>>)(Object) TweedEntryReader.class, readerFactories, spec, context))
|
||||
return Optional.ofNullable(spec)
|
||||
.map(s -> resolveReaderWriterFromSpec((Class<TweedEntryReader<?, ?>>)(Object) TweedEntryReader.class, readerFactories, s, context))
|
||||
.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
|
||||
TweedEntryWriter<?, ?> writer = Optional.ofNullable(writerSpec)
|
||||
.map((spec) -> resolveReaderWriterFromSpec((Class<TweedEntryWriter<?, ?>>)(Object) TweedEntryWriter.class, writerFactories, spec, context))
|
||||
return Optional.ofNullable(spec)
|
||||
.map(s -> resolveReaderWriterFromSpec((Class<TweedEntryWriter<?, ?>>)(Object) TweedEntryWriter.class, writerFactories, s, context))
|
||||
.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) {
|
||||
|
||||
@@ -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.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.weaver.pojo.api.annotation.CollectionWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry;
|
||||
@@ -24,24 +25,27 @@ public class CollectionPojoWeaver implements TweedPojoWeaver {
|
||||
.collectionEntryClass(CollectionConfigEntryImpl.class)
|
||||
.build();
|
||||
|
||||
private RegisteredExtensionData<WeavingContext.ExtensionsData, CollectionWeavingConfig> weavingConfigAccess;
|
||||
@Nullable
|
||||
private PatchworkPartAccess<CollectionWeavingConfig> weavingConfigAccess;
|
||||
|
||||
@Override
|
||||
public void setup(SetupContext context) {
|
||||
this.weavingConfigAccess = context.registerWeavingContextExtensionData(CollectionWeavingConfig.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||
assert weavingConfigAccess != null;
|
||||
|
||||
List<ActualType<?>> collectionTypeParams = valueType.getTypesOfSuperArguments(Collection.class);
|
||||
if (collectionTypeParams == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CollectionWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
||||
Patchwork newExtensionsData = context.extensionsData().copy();
|
||||
newExtensionsData.set(weavingConfigAccess, weavingConfig);
|
||||
|
||||
IntFunction<Collection<Object>> constructor = getCollectionConstructor(valueType);
|
||||
|
||||
@@ -66,10 +70,10 @@ public class CollectionPojoWeaver implements TweedPojoWeaver {
|
||||
}
|
||||
|
||||
private CollectionWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||
CollectionWeavingConfig parent;
|
||||
if (context.extensionsData().isPatchworkPartSet(CollectionWeavingConfig.class)) {
|
||||
parent = (CollectionWeavingConfig) context.extensionsData();
|
||||
} else {
|
||||
assert weavingConfigAccess != null;
|
||||
|
||||
CollectionWeavingConfig parent = context.extensionsData().get(weavingConfigAccess);
|
||||
if (parent == null) {
|
||||
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.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||
import de.siphalor.tweed5.namingformat.api.NamingFormatCollector;
|
||||
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.entry.WeavableCompoundConfigEntry;
|
||||
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.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -37,7 +37,8 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
.build();
|
||||
|
||||
private final NamingFormatCollector namingFormatCollector = new NamingFormatCollector();
|
||||
private RegisteredExtensionData<WeavingContext.ExtensionsData, CompoundWeavingConfig> weavingConfigAccess;
|
||||
@Nullable
|
||||
private PatchworkPartAccess<CompoundWeavingConfig> weavingConfigAccess;
|
||||
|
||||
public void setup(SetupContext context) {
|
||||
namingFormatCollector.setupFormats();
|
||||
@@ -47,13 +48,15 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
|
||||
@Override
|
||||
public <T> @Nullable ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||
assert weavingConfigAccess != null;
|
||||
|
||||
if (context.annotations().getAnnotation(CompoundWeaving.class) == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CompoundWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
||||
Patchwork newExtensionsData = context.extensionsData().copy();
|
||||
newExtensionsData.set(weavingConfigAccess, weavingConfig);
|
||||
|
||||
PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType());
|
||||
|
||||
@@ -69,10 +72,10 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
}
|
||||
|
||||
private CompoundWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||
CompoundWeavingConfig parent;
|
||||
if (context.extensionsData().isPatchworkPartSet(CompoundWeavingConfig.class)) {
|
||||
parent = (CompoundWeavingConfig) context.extensionsData();
|
||||
} else {
|
||||
assert weavingConfigAccess != null;
|
||||
|
||||
CompoundWeavingConfig parent = context.extensionsData().get(weavingConfigAccess);
|
||||
if (parent == null) {
|
||||
parent = DEFAULT_WEAVING_CONFIG;
|
||||
}
|
||||
|
||||
@@ -143,10 +146,14 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
|
||||
private WeavableCompoundConfigEntry.SubEntry weaveCompoundSubEntry(
|
||||
PojoClassIntrospector.Property property,
|
||||
WeavingContext.ExtensionsData newExtensionsData,
|
||||
Patchwork newExtensionsData,
|
||||
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);
|
||||
|
||||
ConfigEntry<?> subEntry;
|
||||
@@ -189,7 +196,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
private WeavingContext createSubContextForProperty(
|
||||
PojoClassIntrospector.Property property,
|
||||
String name,
|
||||
WeavingContext.ExtensionsData newExtensionsData,
|
||||
Patchwork newExtensionsData,
|
||||
WeavingContext parentContext
|
||||
) {
|
||||
return parentContext.subContextBuilder(name)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||
|
||||
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;
|
||||
|
||||
public interface TweedPojoWeaver extends TweedPojoWeavingFunction {
|
||||
@@ -11,6 +12,6 @@ public interface TweedPojoWeaver extends TweedPojoWeavingFunction {
|
||||
void setup(SetupContext context);
|
||||
|
||||
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;
|
||||
ConfigContainer<?> configContainer;
|
||||
String[] path;
|
||||
ExtensionsData extensionsData;
|
||||
Patchwork extensionsData;
|
||||
AnnotatedElement annotations;
|
||||
|
||||
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) {
|
||||
@@ -40,8 +40,6 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
||||
return weavingFunction.weaveEntry(valueType, context);
|
||||
}
|
||||
|
||||
public interface ExtensionsData extends Patchwork<ExtensionsData> {}
|
||||
|
||||
@Accessors(fluent = true, chain = true)
|
||||
@Setter
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@@ -51,7 +49,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
||||
private final TweedPojoWeavingFunction.NonNull weavingFunction;
|
||||
private final ConfigContainer<?> configContainer;
|
||||
private final String[] path;
|
||||
private ExtensionsData extensionsData;
|
||||
private Patchwork extensionsData;
|
||||
private AnnotatedElement annotations;
|
||||
|
||||
public WeavingContext build() {
|
||||
|
||||
@@ -62,7 +62,7 @@ public class CollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseC
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull T deepCopy(@NotNull T value) {
|
||||
public @NotNull T deepCopy(T value) {
|
||||
T copy = instantiateCollection(value.size());
|
||||
for (E element : value) {
|
||||
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.ConfigEntryVisitor;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -97,7 +98,7 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
||||
public void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) {
|
||||
if (visitor.enterCompoundEntry(this, value)) {
|
||||
subEntries.forEach((key, entry) -> {
|
||||
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.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||
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.WeavingContext;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
|
||||
import lombok.*;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -31,7 +30,8 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
private final ConfigContainer<T> configContainer;
|
||||
private final Collection<TweedPojoWeaver> weavers;
|
||||
private final Collection<TweedPojoWeavingPostProcessor> postProcessors;
|
||||
private PatchworkClass<WeavingContext.ExtensionsData> contextExtensionsDataClass;
|
||||
@Nullable
|
||||
private PatchworkFactory contextExtensionsPatchworkFactory;
|
||||
|
||||
public static <T> TweedPojoWeaverBootstrapper<T> create(Class<T> pojoClass) {
|
||||
PojoWeaving rootWeavingConfig = expectAnnotation(pojoClass, PojoWeaving.class);
|
||||
@@ -88,44 +88,21 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
}
|
||||
|
||||
private void setupWeavers() {
|
||||
Map<Class<?>, RegisteredExtensionDataImpl<?>> registeredExtensions = new HashMap<>();
|
||||
PatchworkFactory.Builder contextExtensionsPatchworkFactoryBuilder = PatchworkFactory.builder();
|
||||
|
||||
TweedPojoWeaver.SetupContext setupContext = new TweedPojoWeaver.SetupContext() {
|
||||
@Override
|
||||
public <E> RegisteredExtensionData<WeavingContext.ExtensionsData, E> registerWeavingContextExtensionData(
|
||||
Class<E> dataClass
|
||||
) {
|
||||
RegisteredExtensionDataImpl<E> registeredExtension = new RegisteredExtensionDataImpl<>();
|
||||
registeredExtensions.put(dataClass, registeredExtension);
|
||||
return registeredExtension;
|
||||
}
|
||||
};
|
||||
TweedPojoWeaver.SetupContext setupContext = contextExtensionsPatchworkFactoryBuilder::registerPart;
|
||||
|
||||
for (TweedPojoWeaver weaver : weavers) {
|
||||
weaver.setup(setupContext);
|
||||
}
|
||||
|
||||
PatchworkClassCreator<WeavingContext.ExtensionsData> weavingContextCreator = PatchworkClassCreator.<WeavingContext.ExtensionsData>builder()
|
||||
.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");
|
||||
}
|
||||
contextExtensionsPatchworkFactory = contextExtensionsPatchworkFactoryBuilder.build();
|
||||
}
|
||||
|
||||
private WeavingContext createWeavingContext() {
|
||||
try {
|
||||
WeavingContext.ExtensionsData extensionsData = (WeavingContext.ExtensionsData) contextExtensionsDataClass.constructor().invoke();
|
||||
assert contextExtensionsPatchworkFactory != null;
|
||||
Patchwork extensionsData = contextExtensionsPatchworkFactory.create();
|
||||
|
||||
return WeavingContext.builder(this::weaveEntry, configContainer)
|
||||
.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.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
||||
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
|
||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig;
|
||||
import lombok.AllArgsConstructor;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@NullUnmarked
|
||||
@@ -27,13 +21,12 @@ class CompoundPojoWeaverTest {
|
||||
|
||||
@Test
|
||||
void weave() {
|
||||
PatchworkFactory.Builder weavingContextExtensionDataFactoryBuilder = PatchworkFactory.builder();
|
||||
|
||||
CompoundPojoWeaver compoundWeaver = new CompoundPojoWeaver();
|
||||
compoundWeaver.setup(new TweedPojoWeaver.SetupContext() {
|
||||
@Override
|
||||
public <E> RegisteredExtensionData<WeavingContext.ExtensionsData, E> registerWeavingContextExtensionData(Class<E> dataClass) {
|
||||
return (patchwork, extension) -> ((ExtensionsDataMock) patchwork).weavingConfig = (CompoundWeavingConfig) extension;
|
||||
}
|
||||
});
|
||||
compoundWeaver.setup(weavingContextExtensionDataFactoryBuilder::registerPart);
|
||||
|
||||
PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build();
|
||||
|
||||
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() {
|
||||
@Override
|
||||
@@ -46,7 +39,7 @@ class CompoundPojoWeaverTest {
|
||||
}
|
||||
}
|
||||
}, mock(ConfigContainer.class))
|
||||
.extensionsData(new ExtensionsDataMock(null))
|
||||
.extensionsData(weavingContextExtensionDataFactory.create())
|
||||
.annotations(Compound.class)
|
||||
.build();
|
||||
|
||||
@@ -86,39 +79,4 @@ class CompoundPojoWeaverTest {
|
||||
public static class InnerValue {
|
||||
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