Refactored validation and stuff
This commit is contained in:
@@ -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.extension.EntryExtensionsData;
|
||||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.core.api.validation.ConfigEntryValueValidationException;
|
|
||||||
|
|
||||||
public interface ConfigEntry<T> {
|
public interface ConfigEntry<T> {
|
||||||
Class<T> valueClass();
|
Class<T> valueClass();
|
||||||
void validate(T value) throws ConfigEntryValueValidationException;
|
|
||||||
|
|
||||||
void seal(ConfigContainer<?> container);
|
void seal(ConfigContainer<?> container);
|
||||||
boolean sealed();
|
boolean sealed();
|
||||||
@@ -14,4 +12,5 @@ public interface ConfigEntry<T> {
|
|||||||
EntryExtensionsData extensionsData();
|
EntryExtensionsData extensionsData();
|
||||||
|
|
||||||
void visitInOrder(ConfigEntryVisitor visitor);
|
void visitInOrder(ConfigEntryVisitor visitor);
|
||||||
|
void visitInOrder(ConfigEntryValueVisitor visitor, T value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,5 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
|||||||
public interface TweedExtensionSetupContext {
|
public interface TweedExtensionSetupContext {
|
||||||
ConfigContainer<?> configContainer();
|
ConfigContainer<?> configContainer();
|
||||||
<E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass);
|
<E> RegisteredExtensionData<EntryExtensionsData, E> registerEntryExtensionData(Class<E> dataClass);
|
||||||
|
void registerExtension(TweedExtension extension);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.siphalor.tweed5.core.api.middleware;
|
package de.siphalor.tweed5.core.api.middleware;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.sort.AcyclicGraphSorter;
|
import de.siphalor.tweed5.core.api.sort.AcyclicGraphSorter;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -10,6 +11,7 @@ import java.util.stream.Stream;
|
|||||||
public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
|
public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
|
||||||
private static final String CONTAINER_ID = "";
|
private static final String CONTAINER_ID = "";
|
||||||
|
|
||||||
|
@Getter
|
||||||
private List<Middleware<M>> middlewares = new ArrayList<>();
|
private List<Middleware<M>> middlewares = new ArrayList<>();
|
||||||
private final Set<String> middlewareIds = new HashSet<>();
|
private final Set<String> middlewareIds = new HashSet<>();
|
||||||
private boolean sealed = false;
|
private boolean sealed = false;
|
||||||
@@ -20,19 +22,37 @@ public class DefaultMiddlewareContainer<M> implements MiddlewareContainer<M> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void register(Middleware<M> middleware) {
|
public void registerAll(Collection<Middleware<M>> middlewares) {
|
||||||
if (sealed) {
|
requireUnsealed();
|
||||||
throw new IllegalStateException("Middleware container has already been sealed");
|
|
||||||
|
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()) {
|
if (middleware.id().isEmpty()) {
|
||||||
throw new IllegalArgumentException("Middleware id cannot be empty");
|
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());
|
throw new IllegalArgumentException("Middleware id already registered: " + middleware.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
middlewares.add(middleware);
|
middlewares.add(middleware);
|
||||||
middlewareIds.add(middleware.id());
|
}
|
||||||
|
|
||||||
|
private void requireUnsealed() {
|
||||||
|
if (sealed) {
|
||||||
|
throw new IllegalStateException("Middleware container has already been sealed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package de.siphalor.tweed5.core.api.middleware;
|
package de.siphalor.tweed5.core.api.middleware;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
public interface MiddlewareContainer<M> extends Middleware<M> {
|
public interface MiddlewareContainer<M> extends Middleware<M> {
|
||||||
|
default void registerAll(Collection<Middleware<M>> middlewares) {
|
||||||
|
middlewares.forEach(this::register);
|
||||||
|
}
|
||||||
void register(Middleware<M> middleware);
|
void register(Middleware<M> middleware);
|
||||||
void seal();
|
void seal();
|
||||||
|
Collection<Middleware<M>> middlewares();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -20,7 +21,7 @@ import java.util.Map;
|
|||||||
public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||||
@Getter
|
@Getter
|
||||||
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
||||||
private final HashMap<Class<? extends TweedExtension>, TweedExtension> extensions = new HashMap<>();
|
private final Map<Class<? extends TweedExtension>, TweedExtension> extensions = new HashMap<>();
|
||||||
private ConfigEntry<T> rootEntry;
|
private ConfigEntry<T> rootEntry;
|
||||||
private PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
private PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
||||||
private Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions;
|
private Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions;
|
||||||
@@ -33,7 +34,11 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
|||||||
@Override
|
@Override
|
||||||
public void registerExtension(TweedExtension extension) {
|
public void registerExtension(TweedExtension extension) {
|
||||||
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
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
|
@Override
|
||||||
@@ -41,6 +46,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
|||||||
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
||||||
registeredEntryDataExtensions = new HashMap<>();
|
registeredEntryDataExtensions = new HashMap<>();
|
||||||
|
|
||||||
|
Collection<TweedExtension> additionalExtensions = new ArrayList<>();
|
||||||
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
|
TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() {
|
||||||
@Override
|
@Override
|
||||||
public ConfigContainer<T> configContainer() {
|
public ConfigContainer<T> configContainer() {
|
||||||
@@ -56,10 +62,26 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
|||||||
registeredEntryDataExtensions.put(dataClass, registered);
|
registeredEntryDataExtensions.put(dataClass, registered);
|
||||||
return registered;
|
return registered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerExtension(TweedExtension extension) {
|
||||||
|
if (!extensions.containsKey(extension.getClass())) {
|
||||||
|
additionalExtensions.add(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (TweedExtension extension : extensions.values()) {
|
Collection<TweedExtension> extensionsToSetup = extensions.values();
|
||||||
extension.setup(extensionSetupContext);
|
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()
|
PatchworkClassCreator<EntryExtensionsData> entryExtensionsDataGenerator = PatchworkClassCreator.<EntryExtensionsData>builder()
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
package de.siphalor.tweed5.core.impl.entry;
|
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.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
|
||||||
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 lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -17,17 +10,12 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
abstract class BaseConfigEntryImpl<T> implements ConfigEntry<T> {
|
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
|
@NotNull
|
||||||
private final Class<T> valueClass;
|
private final Class<T> valueClass;
|
||||||
private ConfigContainer<?> container;
|
private ConfigContainer<?> container;
|
||||||
private EntryExtensionsData extensionsData;
|
private EntryExtensionsData extensionsData;
|
||||||
private boolean sealed;
|
private boolean sealed;
|
||||||
private ConfigEntryValidationMiddleware validationMiddleware;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seal(ConfigContainer<?> container) {
|
public void seal(ConfigContainer<?> container) {
|
||||||
@@ -35,18 +23,6 @@ abstract class BaseConfigEntryImpl<T> implements ConfigEntry<T> {
|
|||||||
|
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.extensionsData = container.createExtensionsData();
|
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;
|
sealed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,22 +31,4 @@ abstract class BaseConfigEntryImpl<T> implements ConfigEntry<T> {
|
|||||||
throw new IllegalStateException("Config entry is already sealed!");
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.CoherentCollectionConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -39,4 +40,16 @@ public class CoherentCollectionConfigEntryImpl<E, T extends Collection<E>> exten
|
|||||||
visitor.leaveCollectionEntry(this);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.CompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||||
import de.siphalor.tweed5.core.api.validation.ConfigEntryValueValidationException;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
@@ -52,11 +52,8 @@ public class ReflectiveCompoundConfigEntryImpl<T> extends BaseConfigEntryImpl<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
compoundEntry.configEntry().validate(value);
|
|
||||||
compoundEntry.field().set(compoundValue, value);
|
compoundEntry.field().set(compoundValue, value);
|
||||||
|
|
||||||
} catch (ConfigEntryValueValidationException e) {
|
|
||||||
throw new IllegalArgumentException("Invalid value for config entry: " + key, e);
|
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new IllegalStateException(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
|
@Value
|
||||||
public static class CompoundEntry {
|
public static class CompoundEntry {
|
||||||
String name;
|
String name;
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
package de.siphalor.tweed5.core.impl.entry;
|
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;
|
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||||
|
|
||||||
public class SimpleConfigEntryImpl<T> extends BaseConfigEntryImpl<T> implements SimpleConfigEntry<T> {
|
public class SimpleConfigEntryImpl<T> extends BaseConfigEntryImpl<T> implements SimpleConfigEntry<T> {
|
||||||
public SimpleConfigEntryImpl(Class<T> valueClass) {
|
public SimpleConfigEntryImpl(Class<T> valueClass) {
|
||||||
super(valueClass);
|
super(valueClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInOrder(ConfigEntryVisitor visitor) {
|
||||||
|
visitor.visitEntry(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
||||||
|
visitor.visitEntry(this, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.CompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -64,4 +65,20 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> ext
|
|||||||
visitor.leaveCompoundEntry(this);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
|||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -22,12 +22,12 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
|||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
TweedEntryWriter<Object, ConfigEntry<Object>> innerCasted = (TweedEntryWriter<Object, ConfigEntry<Object>>) inner;
|
TweedEntryWriter<Object, ConfigEntry<Object>> innerCasted = (TweedEntryWriter<Object, ConfigEntry<Object>>) inner;
|
||||||
return (TweedEntryWriter<Object, ConfigEntry<Object>>) (writer, value, entry, context) -> {
|
return (TweedEntryWriter<Object, ConfigEntry<Object>>) (writer, value, entry, context) -> {
|
||||||
if (writer instanceof CompoundDataWriter) {
|
if (writer instanceof CompoundDataVisitor) {
|
||||||
// Comment is already written in front of the key by the CompoundDataWriter,
|
// Comment is already written in front of the key by the CompoundDataWriter,
|
||||||
// so we don't have to write it here.
|
// so we don't have to write it here.
|
||||||
// We also want to unwrap the original writer,
|
// We also want to unwrap the original writer,
|
||||||
// so that the special comment writing is limited to compounds.
|
// so that the special comment writing is limited to compounds.
|
||||||
writer = ((CompoundDataWriter) writer).delegate;
|
writer = ((CompoundDataVisitor) writer).delegate;
|
||||||
} else {
|
} else {
|
||||||
String comment = getEntryComment(entry);
|
String comment = getEntryComment(entry);
|
||||||
if (comment != null) {
|
if (comment != null) {
|
||||||
@@ -37,7 +37,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
|||||||
|
|
||||||
if (entry instanceof CompoundConfigEntry) {
|
if (entry instanceof CompoundConfigEntry) {
|
||||||
innerCasted.write(
|
innerCasted.write(
|
||||||
new CompoundDataWriter(writer, ((CompoundConfigEntry<?>) entry)),
|
new CompoundDataVisitor(writer, ((CompoundConfigEntry<?>) entry)),
|
||||||
value,
|
value,
|
||||||
entry,
|
entry,
|
||||||
context
|
context
|
||||||
@@ -49,8 +49,8 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
private static class CompoundDataWriter implements TweedDataWriter {
|
private static class CompoundDataVisitor implements TweedDataVisitor {
|
||||||
private final TweedDataWriter delegate;
|
private final TweedDataVisitor delegate;
|
||||||
private final CompoundConfigEntry<?> compoundConfigEntry;
|
private final CompoundConfigEntry<?> compoundConfigEntry;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.pather.api;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
public class PathTracking implements PatherData {
|
||||||
|
private final StringBuilder pathBuilder = new StringBuilder(256);
|
||||||
|
private final Deque<Context> contextStack = new ArrayDeque<>(50);
|
||||||
|
private final Deque<String> pathParts = new ArrayDeque<>(50);
|
||||||
|
private final Deque<Integer> listIndexes = new ArrayDeque<>(10);
|
||||||
|
|
||||||
|
public Context currentContext() {
|
||||||
|
return contextStack.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popContext() {
|
||||||
|
if (contextStack.pop() == Context.LIST) {
|
||||||
|
listIndexes.pop();
|
||||||
|
popPathPart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushMapContext() {
|
||||||
|
contextStack.push(Context.MAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushPathPart(String part) {
|
||||||
|
pathParts.push(part);
|
||||||
|
pathBuilder.append(".").append(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popPathPart() {
|
||||||
|
String poppedPart = pathParts.pop();
|
||||||
|
pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushListContext() {
|
||||||
|
contextStack.push(Context.LIST);
|
||||||
|
listIndexes.push(0);
|
||||||
|
pushPathPart("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int incrementListIndex() {
|
||||||
|
int index = listIndexes.pop() + 1;
|
||||||
|
listIndexes.push(index);
|
||||||
|
popPathPart();
|
||||||
|
pushPathPart(Integer.toString(index));
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String valuePath() {
|
||||||
|
return pathBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Context {
|
||||||
|
LIST, MAP,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor {
|
||||||
|
private final ConfigEntryValueVisitor delegate;
|
||||||
|
private final PathTracking pathTracking;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void visitEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
delegate.visitEntry(entry, value);
|
||||||
|
entryVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> boolean enterCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
boolean enter = delegate.enterCollectionEntry(entry, value);
|
||||||
|
if (enter) {
|
||||||
|
pathTracking.pushListContext();
|
||||||
|
}
|
||||||
|
return enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void leaveCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
delegate.leaveCollectionEntry(entry, value);
|
||||||
|
pathTracking.popContext();
|
||||||
|
entryVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> boolean enterCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
boolean enter = delegate.enterCompoundEntry(entry, value);
|
||||||
|
if (enter) {
|
||||||
|
pathTracking.pushMapContext();
|
||||||
|
}
|
||||||
|
return enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enterCompoundSubEntry(String key) {
|
||||||
|
boolean enter = delegate.enterCompoundSubEntry(key);
|
||||||
|
if (enter) {
|
||||||
|
pathTracking.pushPathPart(key);
|
||||||
|
}
|
||||||
|
return enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveCompoundSubEntry(String key) {
|
||||||
|
delegate.leaveCompoundSubEntry(key);
|
||||||
|
pathTracking.popPathPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void leaveCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
delegate.leaveCompoundEntry(entry, value);
|
||||||
|
pathTracking.popContext();
|
||||||
|
entryVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryVisited() {
|
||||||
|
if (pathTracking.currentContext() == PathTracking.Context.LIST) {
|
||||||
|
pathTracking.incrementListIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.pather.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PathTrackingConfigEntryVisitor implements ConfigEntryVisitor {
|
||||||
|
private final ConfigEntryVisitor delegate;
|
||||||
|
private final PathTracking pathTracking;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEntry(ConfigEntry<?> entry) {
|
||||||
|
delegate.visitEntry(entry);
|
||||||
|
entryVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enterCollectionEntry(ConfigEntry<?> entry) {
|
||||||
|
boolean enter = delegate.enterCollectionEntry(entry);
|
||||||
|
if (enter) {
|
||||||
|
pathTracking.pushListContext();
|
||||||
|
}
|
||||||
|
return enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveCollectionEntry(ConfigEntry<?> entry) {
|
||||||
|
delegate.leaveCollectionEntry(entry);
|
||||||
|
pathTracking.popContext();
|
||||||
|
entryVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enterCompoundEntry(ConfigEntry<?> entry) {
|
||||||
|
boolean enter = delegate.enterCompoundEntry(entry);
|
||||||
|
if (enter) {
|
||||||
|
pathTracking.pushMapContext();
|
||||||
|
}
|
||||||
|
return enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enterCompoundSubEntry(String key) {
|
||||||
|
boolean enter = delegate.enterCompoundSubEntry(key);
|
||||||
|
if (enter) {
|
||||||
|
pathTracking.pushPathPart(key);
|
||||||
|
}
|
||||||
|
return enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveCompoundSubEntry(String key) {
|
||||||
|
delegate.leaveCompoundSubEntry(key);
|
||||||
|
pathTracking.popPathPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveCompoundEntry(ConfigEntry<?> entry) {
|
||||||
|
delegate.leaveCompoundEntry(entry);
|
||||||
|
pathTracking.popContext();
|
||||||
|
entryVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryVisited() {
|
||||||
|
if (pathTracking.currentContext() == PathTracking.Context.LIST) {
|
||||||
|
pathTracking.incrementListIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.pather.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataToken;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PathTrackingDataReader implements TweedDataReader {
|
||||||
|
private final TweedDataReader delegate;
|
||||||
|
private final PathTracking pathTracking;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedDataToken peekToken() throws TweedDataReadException {
|
||||||
|
return delegate.peekToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedDataToken readToken() throws TweedDataReadException {
|
||||||
|
TweedDataToken token = delegate.readToken();
|
||||||
|
if (token.isListStart()) {
|
||||||
|
pathTracking.pushListContext();
|
||||||
|
} else if (token.isListValue()) {
|
||||||
|
pathTracking.incrementListIndex();
|
||||||
|
} else if (token.isListEnd()) {
|
||||||
|
pathTracking.popContext();
|
||||||
|
} else if (token.isMapStart()) {
|
||||||
|
pathTracking.pushMapContext();
|
||||||
|
pathTracking.pushPathPart("$");
|
||||||
|
} else if (token.isMapEntryKey()) {
|
||||||
|
pathTracking.popPathPart();
|
||||||
|
pathTracking.pushPathPart(token.readAsString());
|
||||||
|
} else if (token.isMapEnd()) {
|
||||||
|
pathTracking.popPathPart();
|
||||||
|
pathTracking.popContext();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.pather.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PathTrackingDataVisitor implements TweedDataVisitor {
|
||||||
|
private final TweedDataVisitor delegate;
|
||||||
|
private final PathTracking pathTracking;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitNull() {
|
||||||
|
delegate.visitNull();
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitBoolean(boolean value) {
|
||||||
|
delegate.visitBoolean(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitByte(byte value) {
|
||||||
|
delegate.visitByte(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitShort(short value) {
|
||||||
|
delegate.visitShort(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInt(int value) {
|
||||||
|
delegate.visitInt(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLong(long value) {
|
||||||
|
delegate.visitLong(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitFloat(float value) {
|
||||||
|
delegate.visitFloat(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitDouble(double value) {
|
||||||
|
delegate.visitDouble(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitString(@NotNull String value) {
|
||||||
|
delegate.visitString(value);
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void valueVisited() {
|
||||||
|
if (pathTracking.currentContext() == PathTracking.Context.LIST) {
|
||||||
|
pathTracking.incrementListIndex();
|
||||||
|
} else {
|
||||||
|
pathTracking.popPathPart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitListStart() {
|
||||||
|
delegate.visitListStart();
|
||||||
|
pathTracking.pushListContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitListEnd() {
|
||||||
|
delegate.visitListEnd();
|
||||||
|
pathTracking.popContext();
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMapStart() {
|
||||||
|
delegate.visitMapStart();
|
||||||
|
pathTracking.pushMapContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMapEntryKey(String key) {
|
||||||
|
delegate.visitMapEntryKey(key);
|
||||||
|
pathTracking.pushPathPart(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMapEnd() {
|
||||||
|
delegate.visitMapEnd();
|
||||||
|
pathTracking.popContext();
|
||||||
|
valueVisited();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitComment(String comment) {
|
||||||
|
delegate.visitComment(comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.pather.impl;
|
||||||
|
|
||||||
|
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.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.PathTracking;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataReader;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataVisitor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class PatherExtension implements TweedExtension, ReadWriteRelatedExtension {
|
||||||
|
private static final String PATHER_ID = "pather";
|
||||||
|
|
||||||
|
private RegisteredExtensionData<ReadWriteContextExtensionsData, PathTracking> rwContextPathTrackingData;
|
||||||
|
private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware;
|
||||||
|
private Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PATHER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
||||||
|
rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PathTracking.class);
|
||||||
|
|
||||||
|
entryReaderMiddleware = createEntryReaderMiddleware();
|
||||||
|
entryWriterMiddleware = createEntryWriterMiddleware();
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull Middleware<TweedEntryReader<?, ?>> createEntryReaderMiddleware() {
|
||||||
|
return new Middleware<TweedEntryReader<?, ?>>() {
|
||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return PATHER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
||||||
|
//noinspection unchecked
|
||||||
|
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
|
||||||
|
|
||||||
|
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
|
||||||
|
if (context.extensionsData().isPatchworkPartSet(PathTracking.class)) {
|
||||||
|
return castedInner.read(reader, entry, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathTracking pathTracking = new PathTracking();
|
||||||
|
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
|
||||||
|
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Middleware<TweedEntryWriter<?, ?>> createEntryWriterMiddleware() {
|
||||||
|
return new Middleware<TweedEntryWriter<?, ?>>() {
|
||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return PATHER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
|
||||||
|
//noinspection unchecked
|
||||||
|
TweedEntryWriter<Object, ConfigEntry<Object>> castedInner = (TweedEntryWriter<Object, ConfigEntry<Object>>) inner;
|
||||||
|
|
||||||
|
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) -> {
|
||||||
|
if (context.extensionsData().isPatchworkPartSet(PathTracking.class)) {
|
||||||
|
castedInner.write(writer, value, entry, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathTracking pathTracking = new PathTracking();
|
||||||
|
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
|
||||||
|
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware() {
|
||||||
|
return entryReaderMiddleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware() {
|
||||||
|
return entryWriterMiddleware;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface ConfigEntryValidator {
|
||||||
|
<T> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value);
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
<T> String description(ConfigEntry<T> configEntry);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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.defaultextensions.validation.api.result.ValidationIssues;
|
||||||
|
|
||||||
|
public interface ValidationExtension extends TweedExtension {
|
||||||
|
<T> ValidationIssues validate(ConfigEntry<T> entry, T value);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
|
|
||||||
|
public interface ValidationProvidingExtension {
|
||||||
|
Middleware<ConfigEntryValidator> validationMiddleware();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api.result;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class ValidationIssue {
|
||||||
|
String message;
|
||||||
|
ValidationIssueLevel level;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api.result;
|
||||||
|
|
||||||
|
public enum ValidationIssueLevel {
|
||||||
|
INFO,
|
||||||
|
WARN,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api.result;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension data for {@link de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData}
|
||||||
|
* that collects all validation issues.
|
||||||
|
*/
|
||||||
|
public interface ValidationIssues {
|
||||||
|
Map<String, EntryIssues> issuesByPath();
|
||||||
|
|
||||||
|
@Value
|
||||||
|
class EntryIssues {
|
||||||
|
ConfigEntry<?> entry;
|
||||||
|
Collection<ValidationIssue> issues;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api.result;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class ValidationResult<T> {
|
||||||
|
private final T value;
|
||||||
|
@NotNull
|
||||||
|
private final Collection<ValidationIssue> issues;
|
||||||
|
private final boolean hasError;
|
||||||
|
|
||||||
|
public static <T> ValidationResult<T> ok(T value) {
|
||||||
|
return new ValidationResult<>(value, Collections.emptyList(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ValidationResult<T> withIssues(T value, @NotNull Collection<ValidationIssue> issues) {
|
||||||
|
return new ValidationResult<>(value, issues, issuesContainError(issues));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean issuesContainError(Collection<ValidationIssue> issues) {
|
||||||
|
if (issues.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ValidationIssue issue : issues) {
|
||||||
|
if (issue.level() == ValidationIssueLevel.ERROR) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationResult<T> andThen(Function<T, ValidationResult<T>> function) {
|
||||||
|
if (hasError) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult<T> functionResult = function.apply(value);
|
||||||
|
|
||||||
|
if (functionResult.issues.isEmpty()) {
|
||||||
|
if (functionResult.value == value) {
|
||||||
|
return this;
|
||||||
|
} else if (issues.isEmpty()) {
|
||||||
|
return new ValidationResult<>(functionResult.value, Collections.emptyList(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<ValidationIssue> combinedIssues = new ArrayList<>(issues.size() + functionResult.issues.size());
|
||||||
|
combinedIssues.addAll(issues);
|
||||||
|
combinedIssues.addAll(functionResult.issues);
|
||||||
|
|
||||||
|
return new ValidationResult<>(functionResult.value, combinedIssues, functionResult.hasError);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api.validators;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
|
||||||
|
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 lombok.AllArgsConstructor;
|
||||||
|
import lombok.Value;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class NumberRangeValidator<N extends Number> implements ConfigEntryValidator {
|
||||||
|
@NotNull
|
||||||
|
Class<N> numberClass;
|
||||||
|
@Nullable
|
||||||
|
N minimum;
|
||||||
|
@Nullable
|
||||||
|
N maximum;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {
|
||||||
|
if (!(value instanceof Number)) {
|
||||||
|
return ValidationResult.withIssues(value, Collections.singleton(
|
||||||
|
new ValidationIssue("Value must be numeric", ValidationIssueLevel.ERROR)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (value.getClass() != numberClass) {
|
||||||
|
return ValidationResult.withIssues(value, Collections.singleton(
|
||||||
|
new ValidationIssue(
|
||||||
|
"Value is of wrong type, expected " + numberClass.getSimpleName() +
|
||||||
|
", got " + value.getClass().getSimpleName(),
|
||||||
|
ValidationIssueLevel.ERROR
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Number numberValue = (Number) value;
|
||||||
|
if (minimum != null && compare(numberValue, minimum) < 0) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return ValidationResult.withIssues((T) minimum, Collections.singleton(
|
||||||
|
new ValidationIssue("Value must be at least " + minimum, ValidationIssueLevel.WARN)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (maximum != null && compare(numberValue, maximum) > 0) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return ValidationResult.withIssues((T) maximum, Collections.singleton(
|
||||||
|
new ValidationIssue("Value must be at most " + maximum, ValidationIssueLevel.WARN)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult.ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compare(@NotNull Number a, @NotNull Number b) {
|
||||||
|
if (numberClass == Byte.class) {
|
||||||
|
return Byte.compare(a.byteValue(), b.byteValue());
|
||||||
|
} else if (numberClass == Short.class) {
|
||||||
|
return Short.compare(a.shortValue(), b.shortValue());
|
||||||
|
} else if (numberClass == Integer.class) {
|
||||||
|
return Integer.compare(a.intValue(), b.intValue());
|
||||||
|
} else if (numberClass == Long.class) {
|
||||||
|
return Long.compare(a.longValue(), b.longValue());
|
||||||
|
} else if (numberClass == Float.class) {
|
||||||
|
return Float.compare(a.floatValue(), b.floatValue());
|
||||||
|
} else {
|
||||||
|
return Double.compare(a.doubleValue(), b.doubleValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
|
||||||
|
if (minimum == null) {
|
||||||
|
if (maximum == null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return "Must be smaller or equal to " + maximum + ".";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (maximum == null) {
|
||||||
|
return "Must be greater or equal to " + minimum + ".";
|
||||||
|
} else {
|
||||||
|
return "Must be inclusively between " + minimum + " and " + maximum + ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.api.validators;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SimpleValidatorMiddleware implements Middleware<ConfigEntryValidator> {
|
||||||
|
String id;
|
||||||
|
ConfigEntryValidator validator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigEntryValidator process(ConfigEntryValidator inner) {
|
||||||
|
return new ConfigEntryValidator() {
|
||||||
|
@Override
|
||||||
|
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {
|
||||||
|
return inner.validate(configEntry, value).andThen(v -> validator.validate(configEntry, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
|
||||||
|
String description = validator.description(configEntry);
|
||||||
|
if (description.isEmpty()) {
|
||||||
|
return inner.description(configEntry);
|
||||||
|
}
|
||||||
|
return inner.description(configEntry) + "\n" + description;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
|
||||||
|
|
||||||
|
public interface InternalValidationEntryData {
|
||||||
|
ConfigEntryValidator completeEntryValidator();
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.impl;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import de.siphalor.tweed5.core.api.middleware.Middleware;
|
||||||
|
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;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
|
||||||
|
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.impl.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.ValidationIssues;
|
||||||
|
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 lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.var;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ValidationExtensionImpl implements ReadWriteRelatedExtension, ValidationExtension, CommentModifyingExtension {
|
||||||
|
private static final ValidationResult<?> PRIMITIVE_IS_NULL_RESULT = ValidationResult.withIssues(
|
||||||
|
null,
|
||||||
|
Collections.singletonList(new ValidationIssue("Primitive value must not be null", ValidationIssueLevel.ERROR))
|
||||||
|
);
|
||||||
|
private static final ConfigEntryValidator PRIMITIVE_VALIDATOR = new ConfigEntryValidator() {
|
||||||
|
@Override
|
||||||
|
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, T value) {
|
||||||
|
if (value == null) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (ValidationResult<T>) PRIMITIVE_IS_NULL_RESULT;
|
||||||
|
}
|
||||||
|
return ValidationResult.ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull <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(ConfigEntry<T> configEntry, T value) {
|
||||||
|
return ValidationResult.ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
|
||||||
|
private MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer;
|
||||||
|
private EntryValidationReaderMiddleware readerMiddleware;
|
||||||
|
private RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues> readContextValidationIssuesExtensionData;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "validation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(TweedExtensionSetupContext context) {
|
||||||
|
context.registerExtension(new PatherExtension());
|
||||||
|
|
||||||
|
validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class);
|
||||||
|
context.registerEntryExtensionData(EntrySpecificValidation.class);
|
||||||
|
|
||||||
|
entryValidatorMiddlewareContainer = new DefaultMiddlewareContainer<>();
|
||||||
|
for (TweedExtension extension : context.configContainer().extensions()) {
|
||||||
|
if (extension instanceof ValidationProvidingExtension) {
|
||||||
|
entryValidatorMiddlewareContainer.register(((ValidationProvidingExtension) extension).validationMiddleware());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entryValidatorMiddlewareContainer.seal();
|
||||||
|
|
||||||
|
readerMiddleware = new EntryValidationReaderMiddleware();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Middleware<CommentProducer> commentMiddleware() {
|
||||||
|
return new Middleware<CommentProducer>() {
|
||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return "validation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommentProducer process(CommentProducer inner) {
|
||||||
|
return entry -> {
|
||||||
|
String baseComment = inner.createComment(entry);
|
||||||
|
if (entry.extensionsData().isPatchworkPartSet(InternalValidationEntryData.class)) {
|
||||||
|
String validationDescription = ((InternalValidationEntryData) entry.extensionsData())
|
||||||
|
.completeEntryValidator()
|
||||||
|
.description(entry)
|
||||||
|
.trim();
|
||||||
|
if (!validationDescription.isEmpty()) {
|
||||||
|
baseComment += "\n\n" + validationDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baseComment;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
|
||||||
|
readContextValidationIssuesExtensionData = context.registerReadWriteContextExtensionData(ValidationIssues.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initEntry(ConfigEntry<?> configEntry) {
|
||||||
|
ConfigEntryValidator baseValidator;
|
||||||
|
if (configEntry.valueClass().isPrimitive()) {
|
||||||
|
baseValidator = PRIMITIVE_VALIDATOR;
|
||||||
|
} else {
|
||||||
|
baseValidator = NOOP_VALIDATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigEntryValidator entryValidator;
|
||||||
|
var entrySpecificValidators = getEntrySpecificValidators(configEntry);
|
||||||
|
if (entrySpecificValidators.isEmpty()) {
|
||||||
|
entryValidator = entryValidatorMiddlewareContainer.process(baseValidator);
|
||||||
|
} else {
|
||||||
|
DefaultMiddlewareContainer<ConfigEntryValidator> entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>();
|
||||||
|
entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares());
|
||||||
|
entrySpecificValidatorContainer.registerAll(entrySpecificValidators);
|
||||||
|
entrySpecificValidatorContainer.seal();
|
||||||
|
entryValidator = entrySpecificValidatorContainer.process(baseValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
validationEntryDataExtension.set(configEntry.extensionsData(), new InternalValidationEntryDataImpl(entryValidator));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<Middleware<ConfigEntryValidator>> getEntrySpecificValidators(ConfigEntry<?> configEntry) {
|
||||||
|
if (!configEntry.extensionsData().isPatchworkPartSet(EntrySpecificValidation.class)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return ((EntrySpecificValidation) configEntry.extensionsData()).validators();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware() {
|
||||||
|
return readerMiddleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ValidationIssues validate(ConfigEntry<T> entry, T value) {
|
||||||
|
PathTracking pathTracking = new PathTracking();
|
||||||
|
ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking);
|
||||||
|
|
||||||
|
entry.visitInOrder(new PathTrackingConfigEntryValueVisitor(validatingVisitor, pathTracking), value);
|
||||||
|
|
||||||
|
return validatingVisitor.validationIssues();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
private static class InternalValidationEntryDataImpl implements InternalValidationEntryData {
|
||||||
|
ConfigEntryValidator completeEntryValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EntryValidationReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
|
||||||
|
@Override
|
||||||
|
public String id() {
|
||||||
|
return "validation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> mustComeAfter() {
|
||||||
|
return Collections.singleton("pather");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
|
||||||
|
//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 = 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);
|
||||||
|
|
||||||
|
if (!validationResult.issues().isEmpty() && context.extensionsData().isPatchworkPartSet(PatherData.class)) {
|
||||||
|
String path = ((PatherData) context.extensionsData()).valuePath();
|
||||||
|
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
|
||||||
|
entry,
|
||||||
|
validationResult.issues()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationResult.hasError()) {
|
||||||
|
throw new TweedEntryReadException("Failed to validate entry: " + validationResult.issues());
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationResult.value();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
private static 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);
|
||||||
|
if (!result.issues().isEmpty()) {
|
||||||
|
validationIssues.issuesByPath().put(pathTracking.valuePath(), new ValidationIssues.EntryIssues(entry, result.issues()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> boolean enterCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void leaveCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
visitEntry(entry, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> boolean enterCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void leaveCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||||
|
visitEntry(entry, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
private static class ValidationIssuesImpl implements ValidationIssues {
|
||||||
|
Map<String, EntryIssues> issuesByPath = new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package de.siphalor.tweed5.defaultextensions.validation.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.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.defaultextensions.comment.api.AComment;
|
||||||
|
import de.siphalor.tweed5.defaultextensions.comment.impl.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.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.lang.annotation.Annotation;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class ValidationExtensionImplTest {
|
||||||
|
private DefaultConfigContainer<Map<String, Object>> configContainer;
|
||||||
|
private CommentExtension commentExtension;
|
||||||
|
private ValidationExtension validationExtension;
|
||||||
|
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
|
||||||
|
private SimpleConfigEntryImpl<Byte> byteEntry;
|
||||||
|
private SimpleConfigEntryImpl<Integer> intEntry;
|
||||||
|
private SimpleConfigEntryImpl<Double> doubleEntry;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
configContainer = new DefaultConfigContainer<>();
|
||||||
|
|
||||||
|
commentExtension = new CommentExtension();
|
||||||
|
configContainer.registerExtension(commentExtension);
|
||||||
|
validationExtension = new ValidationExtensionImpl();
|
||||||
|
configContainer.registerExtension(validationExtension);
|
||||||
|
configContainer.finishExtensionSetup();
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
rootEntry = new StaticMapCompoundConfigEntryImpl<>(((Class<Map<String, Object>>) (Class<?>) Map.class), LinkedHashMap::new);
|
||||||
|
|
||||||
|
byteEntry = new SimpleConfigEntryImpl<>(Byte.class);
|
||||||
|
rootEntry.addSubEntry("byte", byteEntry);
|
||||||
|
intEntry = new SimpleConfigEntryImpl<>(Integer.class);
|
||||||
|
rootEntry.addSubEntry("int", intEntry);
|
||||||
|
doubleEntry = new SimpleConfigEntryImpl<>(Double.class);
|
||||||
|
rootEntry.addSubEntry("double", doubleEntry);
|
||||||
|
|
||||||
|
configContainer.attachAndSealTree(rootEntry);
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
RegisteredExtensionData<EntryExtensionsData, AComment> commentData = (RegisteredExtensionData<EntryExtensionsData, AComment>) configContainer.entryDataExtensions().get(AComment.class);
|
||||||
|
commentData.set(intEntry.extensionsData(), new AComment() {
|
||||||
|
@Override
|
||||||
|
public Class<? extends Annotation> annotationType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String value() {
|
||||||
|
return "This is the main comment!";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//noinspection unchecked
|
||||||
|
RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class);
|
||||||
|
entrySpecificValidation.set(byteEntry.extensionsData(), new EntrySpecificValidation() {
|
||||||
|
@Override
|
||||||
|
public Collection<Middleware<ConfigEntryValidator>> validators() {
|
||||||
|
return Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
entrySpecificValidation.set(intEntry.extensionsData(), new EntrySpecificValidation() {
|
||||||
|
@Override
|
||||||
|
public Collection<Middleware<ConfigEntryValidator>> validators() {
|
||||||
|
return Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Integer.class, null, 123)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
entrySpecificValidation.set(doubleEntry.extensionsData(), new EntrySpecificValidation() {
|
||||||
|
@Override
|
||||||
|
public Collection<Middleware<ConfigEntryValidator>> validators() {
|
||||||
|
return Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Double.class, 0.5, null)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
configContainer.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({
|
||||||
|
"12, 34, 56.78"
|
||||||
|
})
|
||||||
|
void valid(Byte b, Integer i, Double d) {
|
||||||
|
HashMap<String, Object> value = new HashMap<>();
|
||||||
|
value.put("byte", b);
|
||||||
|
value.put("int", i);
|
||||||
|
value.put("double", d);
|
||||||
|
|
||||||
|
ValidationIssues result = validationExtension.validate(rootEntry, value);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotNull(result.issuesByPath());
|
||||||
|
assertTrue(result.issuesByPath().isEmpty(), () -> "Should have no issues, but got " + result.issuesByPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void invalid() {
|
||||||
|
HashMap<String, Object> value = new HashMap<>();
|
||||||
|
value.put("byte", (byte) 9);
|
||||||
|
value.put("int", 124);
|
||||||
|
value.put("double", 0.2);
|
||||||
|
|
||||||
|
ValidationIssues result = validationExtension.validate(rootEntry, value);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotNull(result.issuesByPath());
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
() -> assertValidationIssue(result, ".byte", byteEntry, new ValidationIssue("Value must be at least 10", ValidationIssueLevel.WARN)),
|
||||||
|
() -> assertValidationIssue(result, ".int", intEntry, new ValidationIssue("Value must be at most 123", ValidationIssueLevel.WARN)),
|
||||||
|
() -> assertValidationIssue(result, ".double", doubleEntry, new ValidationIssue("Value must be at least 0.5", ValidationIssueLevel.WARN))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertValidationIssue(
|
||||||
|
ValidationIssues issues,
|
||||||
|
String expectedPath,
|
||||||
|
ConfigEntry<?> expectedEntry,
|
||||||
|
ValidationIssue expectedIssue
|
||||||
|
) {
|
||||||
|
assertTrue(issues.issuesByPath().containsKey(expectedPath), "Must have issues for path " + expectedPath);
|
||||||
|
ValidationIssues.EntryIssues entryIssues = issues.issuesByPath().get(expectedPath);
|
||||||
|
assertSame(expectedEntry, entryIssues.entry(), "Entry must match");
|
||||||
|
assertEquals(1, entryIssues.issues().size(), "Entry must have exactly one issue");
|
||||||
|
assertEquals(expectedIssue, entryIssues.issues().iterator().next(), "Issue must match");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package de.siphalor.tweed5.dataapi.api;
|
|
||||||
|
|
||||||
public interface TweedDataWriter extends TweedDataVisitor {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,12 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
|||||||
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
import de.siphalor.tweed5.core.api.extension.TweedExtension;
|
||||||
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
|
|
||||||
public interface ReadWriteExtension extends TweedExtension {
|
public interface ReadWriteExtension extends TweedExtension {
|
||||||
ReadWriteContextExtensionsData createReadWriteContextExtensionsData();
|
ReadWriteContextExtensionsData createReadWriteContextExtensionsData();
|
||||||
|
|
||||||
<T> T read(TweedDataReader reader, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryReadException;
|
<T> T read(TweedDataReader reader, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryReadException;
|
||||||
|
|
||||||
<T> void write(TweedDataWriter writer, T value, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException;
|
<T> void write(TweedDataVisitor writer, T value, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package de.siphalor.tweed5.data.extension.api;
|
|||||||
|
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface TweedEntryWriter<T, C extends ConfigEntry<T>> {
|
public interface TweedEntryWriter<T, C extends ConfigEntry<T>> {
|
||||||
void write(TweedDataWriter writer, T value, C entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException;
|
void write(TweedDataVisitor writer, T value, C entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension
|
|||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||||
@@ -132,7 +132,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void write(TweedDataWriter writer, T value, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException {
|
public <T> void write(TweedDataVisitor writer, T value, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException {
|
||||||
try {
|
try {
|
||||||
getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(contextExtensionsData));
|
getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(contextExtensionsData));
|
||||||
} catch (TweedDataWriteException e) {
|
} catch (TweedDataWriteException e) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
private static class PrimitiveReaderWriter<T> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
|
private static class PrimitiveReaderWriter<T> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
|
||||||
private final Function<TweedDataToken, T> readerCall;
|
private final Function<TweedDataToken, T> readerCall;
|
||||||
private final BiConsumer<TweedDataWriter, T> writerCall;
|
private final BiConsumer<TweedDataVisitor, T> writerCall;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) throws TweedDataReadException {
|
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) throws TweedDataReadException {
|
||||||
@@ -42,7 +42,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(TweedDataWriter writer, T value, ConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
public void write(TweedDataVisitor writer, T value, ConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
||||||
requireNonNullWriteValue(value);
|
requireNonNullWriteValue(value);
|
||||||
writerCall.accept(writer, value);
|
writerCall.accept(writer, value);
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(TweedDataWriter writer, C value, CoherentCollectionConfigEntry<T, C> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
public void write(TweedDataVisitor writer, C value, CoherentCollectionConfigEntry<T, C> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
||||||
requireNonNullWriteValue(value);
|
requireNonNullWriteValue(value);
|
||||||
|
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
@@ -126,7 +126,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(TweedDataWriter writer, T value, CompoundConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
public void write(TweedDataVisitor writer, T value, CompoundConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
||||||
requireNonNullWriteValue(value);
|
requireNonNullWriteValue(value);
|
||||||
|
|
||||||
writer.visitMapStart();
|
writer.visitMapStart();
|
||||||
@@ -178,7 +178,7 @@ public class TweedEntryReaderWriterImpls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(TweedDataWriter writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) throws TweedDataWriteException {
|
public void write(TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) throws TweedDataWriteException {
|
||||||
writer.visitNull();
|
writer.visitNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
|
|||||||
import de.siphalor.tweed5.data.hjson.HjsonLexer;
|
import de.siphalor.tweed5.data.hjson.HjsonLexer;
|
||||||
import de.siphalor.tweed5.data.hjson.HjsonReader;
|
import de.siphalor.tweed5.data.hjson.HjsonReader;
|
||||||
import de.siphalor.tweed5.data.hjson.HjsonWriter;
|
import de.siphalor.tweed5.data.hjson.HjsonWriter;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -96,7 +96,7 @@ class ReadWriteExtensionImplTest {
|
|||||||
assertEquals(Arrays.asList(true, false, true), result.get("list"));
|
assertEquals(Arrays.asList(true, false, true), result.get("list"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TweedDataWriter setupWriter(Function<Writer, TweedDataWriter> writerFactory) {
|
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {
|
||||||
stringWriter = new StringWriter();
|
stringWriter = new StringWriter();
|
||||||
return writerFactory.apply(stringWriter);
|
return writerFactory.apply(stringWriter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package de.siphalor.tweed5.data.hjson;
|
package de.siphalor.tweed5.data.hjson;
|
||||||
|
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
||||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import java.util.*;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class HjsonWriter implements TweedDataWriter {
|
public class HjsonWriter implements TweedDataVisitor {
|
||||||
private static final int PREFILL_INDENT = 10;
|
private static final int PREFILL_INDENT = 10;
|
||||||
private static final Pattern LINE_FEED_PATTERN = Pattern.compile("\\n|\\r\\n");
|
private static final Pattern LINE_FEED_PATTERN = Pattern.compile("\\n|\\r\\n");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user