Refactored validation and stuff

This commit is contained in:
2024-06-09 18:51:10 +02:00
parent b0f35b03b9
commit 31d905b065
42 changed files with 1309 additions and 119 deletions

View File

@@ -2,11 +2,9 @@ package de.siphalor.tweed5.core.api.entry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.validation.ConfigEntryValueValidationException;
public interface ConfigEntry<T> {
Class<T> valueClass();
void validate(T value) throws ConfigEntryValueValidationException;
void seal(ConfigContainer<?> container);
boolean sealed();
@@ -14,4 +12,5 @@ public interface ConfigEntry<T> {
EntryExtensionsData extensionsData();
void visitInOrder(ConfigEntryVisitor visitor);
void visitInOrder(ConfigEntryValueVisitor visitor, T value);
}

View File

@@ -0,0 +1,28 @@
package de.siphalor.tweed5.core.api.entry;
public interface ConfigEntryValueVisitor {
<T> void visitEntry(ConfigEntry<T> entry, T value);
default <T> boolean enterCollectionEntry(ConfigEntry<T> entry, T value) {
visitEntry(entry, value);
return true;
}
default <T> void leaveCollectionEntry(ConfigEntry<T> entry, T value) {
}
default <T> boolean enterCompoundEntry(ConfigEntry<T> entry, T value) {
visitEntry(entry, value);
return true;
}
default boolean enterCompoundSubEntry(String key) {
return true;
}
default void leaveCompoundSubEntry(String key) {
}
default <T> void leaveCompoundEntry(ConfigEntry<T> entry, T value) {
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.core.api.middleware;
import de.siphalor.tweed5.core.api.sort.AcyclicGraphSorter;
import lombok.Getter;
import java.util.*;
import java.util.function.Function;
@@ -10,6 +11,7 @@ import java.util.stream.Stream;
public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
private static final String CONTAINER_ID = "";
@Getter
private List<Middleware<M>> middlewares = new ArrayList<>();
private final Set<String> middlewareIds = new HashSet<>();
private boolean sealed = false;
@@ -20,19 +22,37 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
}
@Override
public void register(Middleware<M> middleware) {
if (sealed) {
throw new IllegalStateException("Middleware container has already been sealed");
public void registerAll(Collection<Middleware<M>> middlewares) {
requireUnsealed();
for (Middleware<M> middleware : middlewares) {
if (middleware.id().isEmpty()) {
throw new IllegalArgumentException("Middleware id cannot be empty");
}
if (!this.middlewareIds.add(middleware.id())) {
throw new IllegalArgumentException("Middleware id already registered: " + middleware.id());
}
}
this.middlewares.addAll(middlewares);
}
@Override
public void register(Middleware<M> middleware) {
requireUnsealed();
if (middleware.id().isEmpty()) {
throw new IllegalArgumentException("Middleware id cannot be empty");
}
if (middlewareIds.contains(middleware.id())) {
if (!middlewareIds.add(middleware.id())) {
throw new IllegalArgumentException("Middleware id already registered: " + middleware.id());
}
middlewares.add(middleware);
middlewareIds.add(middleware.id());
}
private void requireUnsealed() {
if (sealed) {
throw new IllegalStateException("Middleware container has already been sealed");
}
}
@Override

View File

@@ -1,6 +1,12 @@
package de.siphalor.tweed5.core.api.middleware;
import java.util.Collection;
public interface MiddlewareContainer<M> extends Middleware<M> {
default void registerAll(Collection<Middleware<M>> middlewares) {
middlewares.forEach(this::register);
}
void register(Middleware<M> middleware);
void seal();
Collection<Middleware<M>> middlewares();
}

View File

@@ -1,8 +0,0 @@
package de.siphalor.tweed5.core.api.validation;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
public interface ConfigEntryValidationExtension {
Middleware<ConfigEntryValidationMiddleware> validationMiddleware(ConfigEntry<?> configEntry);
}

View File

@@ -1,8 +0,0 @@
package de.siphalor.tweed5.core.api.validation;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
@FunctionalInterface
public interface ConfigEntryValidationMiddleware {
<T> void validate(ConfigEntry<T> configEntry, T value) throws ConfigEntryValueValidationException;
}

View File

@@ -1,18 +0,0 @@
package de.siphalor.tweed5.core.api.validation;
public class ConfigEntryValueValidationException extends Exception {
public ConfigEntryValueValidationException() {
}
public ConfigEntryValueValidationException(String message) {
super(message);
}
public ConfigEntryValueValidationException(String message, Throwable cause) {
super(message, cause);
}
public ConfigEntryValueValidationException(Throwable cause) {
super(cause);
}
}

View File

@@ -13,6 +13,7 @@ import lombok.Getter;
import lombok.Setter;
import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -20,7 +21,7 @@ import java.util.Map;
public class DefaultConfigContainer<T> implements ConfigContainer<T> {
@Getter
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
private final HashMap<Class<? extends TweedExtension>, TweedExtension> extensions = new HashMap<>();
private final Map<Class<? extends TweedExtension>, TweedExtension> extensions = new HashMap<>();
private ConfigEntry<T> rootEntry;
private PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
private Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions;
@@ -33,7 +34,11 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
@Override
public void registerExtension(TweedExtension extension) {
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
extensions.put(extension.getClass(), extension);
TweedExtension previous = extensions.put(extension.getClass(), extension);
if (previous != null) {
throw new IllegalArgumentException("Extension " + extension.getClass().getName() + " is already registered");
}
}
@Override
@@ -41,6 +46,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
registeredEntryDataExtensions = new HashMap<>();
Collection<TweedExtension> additionalExtensions = new ArrayList<>();
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
@Override
public ConfigContainer<T> configContainer() {
@@ -56,10 +62,26 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
registeredEntryDataExtensions.put(dataClass, registered);
return registered;
}
@Override
public void registerExtension(TweedExtension extension) {
if (!extensions.containsKey(extension.getClass())) {
additionalExtensions.add(extension);
}
}
};
for (TweedExtension extension : extensions.values()) {
extension.setup(extensionSetupContext);
Collection<TweedExtension> extensionsToSetup = extensions.values();
while (!extensionsToSetup.isEmpty()) {
for (TweedExtension extension : extensionsToSetup) {
extension.setup(extensionSetupContext);
}
for (TweedExtension additionalExtension : additionalExtensions) {
extensions.put(additionalExtension.getClass(), additionalExtension);
}
extensionsToSetup = new ArrayList<>(additionalExtensions);
additionalExtensions.clear();
}
PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()

View File

@@ -1,15 +1,8 @@
package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer;
import de.siphalor.tweed5.core.api.validation.ConfigEntryValidationExtension;
import de.siphalor.tweed5.core.api.validation.ConfigEntryValidationMiddleware;
import de.siphalor.tweed5.core.api.validation.ConfigEntryValueValidationException;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@@ -17,17 +10,12 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
@Getter
abstract class BaseConfigEntryImpl<T> implements ConfigEntry<T> {
private static final ConfigEntryValidationMiddleware ROOT_VALIDATION = new ConfigEntryValidationMiddleware() {
@Override
public <U> void validate(ConfigEntry<U> configEntry, U value) {}
};
@NotNull
private final Class<T> valueClass;
private ConfigContainer<?> container;
private EntryExtensionsData extensionsData;
private boolean sealed;
private ConfigEntryValidationMiddleware validationMiddleware;
@Override
public void seal(ConfigContainer<?> container) {
@@ -35,18 +23,6 @@ abstract class BaseConfigEntryImpl<T> implements ConfigEntry<T> {
this.container = container;
this.extensionsData = container.createExtensionsData();
MiddlewareContainer<ConfigEntryValidationMiddleware> validationMiddlewareContainer = new DefaultMiddlewareContainer<>();
for (TweedExtension extension : container().extensions()) {
if (extension instanceof ConfigEntryValidationExtension) {
validationMiddlewareContainer.register(((ConfigEntryValidationExtension) extension).validationMiddleware(this));
}
}
validationMiddlewareContainer.seal();
validationMiddleware = validationMiddlewareContainer.process(ROOT_VALIDATION);
sealed = true;
}
@@ -55,22 +31,4 @@ abstract class BaseConfigEntryImpl<T> implements ConfigEntry<T> {
throw new IllegalStateException("Config entry is already sealed!");
}
}
@Override
public void validate(T value) throws ConfigEntryValueValidationException {
if (value == null) {
if (valueClass.isPrimitive()) {
throw new ConfigEntryValueValidationException("Value must not be null");
}
} else if (!valueClass.isAssignableFrom(value.getClass())) {
throw new ConfigEntryValueValidationException("Value must be of type " + valueClass.getName());
}
validationMiddleware.validate(this, value);
}
@Override
public void visitInOrder(ConfigEntryVisitor visitor) {
visitor.visitEntry(this);
}
}

View File

@@ -2,6 +2,7 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.entry.CoherentCollectionConfigEntry;
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 java.util.Collection;
@@ -39,4 +40,16 @@ public class CoherentCollectionConfigEntryImpl<E, T extends Collection<E>> exten
visitor.leaveCollectionEntry(this);
}
}
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterCollectionEntry(this, value)) {
if (value != null) {
for (E element : value) {
visitor.visitEntry(elementEntry, element);
}
}
visitor.leaveCollectionEntry(this, value);
}
}
}

View File

@@ -2,8 +2,8 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
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.core.api.validation.ConfigEntryValueValidationException;
import lombok.Getter;
import lombok.Value;
@@ -52,11 +52,8 @@ public class ReflectiveCompoundConfigEntryImpl<T> extends BaseConfigEntryImpl<T>
}
try {
compoundEntry.configEntry().validate(value);
compoundEntry.field().set(compoundValue, value);
} catch (ConfigEntryValueValidationException e) {
throw new IllegalArgumentException("Invalid value for config entry: " + key, e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
@@ -99,6 +96,23 @@ public class ReflectiveCompoundConfigEntryImpl<T> extends BaseConfigEntryImpl<T>
}
}
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterCompoundEntry(this, value)) {
compoundEntries.forEach((key, entry) -> {
if (visitor.enterCompoundSubEntry(key)) {
try {
visitor.visitEntry(entry.configEntry(), entry.field().get(value));
} catch (IllegalAccessException ignored) {
// ignored
}
visitor.leaveCompoundSubEntry(key);
}
});
visitor.leaveCompoundEntry(this, value);
}
}
@Value
public static class CompoundEntry {
String name;

View File

@@ -1,9 +1,21 @@
package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
public class SimpleConfigEntryImpl<T> extends BaseConfigEntryImpl<T> implements SimpleConfigEntry<T> {
public SimpleConfigEntryImpl(Class<T> valueClass) {
super(valueClass);
}
@Override
public void visitInOrder(ConfigEntryVisitor visitor) {
visitor.visitEntry(this);
}
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
visitor.visitEntry(this, value);
}
}

View File

@@ -2,6 +2,7 @@ package de.siphalor.tweed5.core.impl.entry;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
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 org.jetbrains.annotations.NotNull;
@@ -64,4 +65,20 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> ext
visitor.leaveCompoundEntry(this);
}
}
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
if (visitor.enterCompoundEntry(this, value)) {
if (value != null) {
compoundEntries.forEach((key, entry) -> {
if (visitor.enterCompoundSubEntry(key)) {
//noinspection unchecked
((ConfigEntry<Object>) entry).visitInOrder(visitor, value.get(key));
visitor.leaveCompoundSubEntry(key);
}
});
}
visitor.leaveCompoundEntry(this, value);
}
}
}