diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java index 16702df..bc15db6 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntry.java @@ -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 { Class valueClass(); - void validate(T value) throws ConfigEntryValueValidationException; void seal(ConfigContainer container); boolean sealed(); @@ -14,4 +12,5 @@ public interface ConfigEntry { EntryExtensionsData extensionsData(); void visitInOrder(ConfigEntryVisitor visitor); + void visitInOrder(ConfigEntryValueVisitor visitor, T value); } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java new file mode 100644 index 0000000..3c7d050 --- /dev/null +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryValueVisitor.java @@ -0,0 +1,28 @@ +package de.siphalor.tweed5.core.api.entry; + +public interface ConfigEntryValueVisitor { + void visitEntry(ConfigEntry entry, T value); + + default boolean enterCollectionEntry(ConfigEntry entry, T value) { + visitEntry(entry, value); + return true; + } + + default void leaveCollectionEntry(ConfigEntry entry, T value) { + } + + default boolean enterCompoundEntry(ConfigEntry entry, T value) { + visitEntry(entry, value); + return true; + } + + default boolean enterCompoundSubEntry(String key) { + return true; + } + + default void leaveCompoundSubEntry(String key) { + } + + default void leaveCompoundEntry(ConfigEntry entry, T value) { + } +} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java index e3f3717..55b75d8 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/extension/TweedExtensionSetupContext.java @@ -5,4 +5,5 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer; public interface TweedExtensionSetupContext { ConfigContainer configContainer(); RegisteredExtensionData registerEntryExtensionData(Class dataClass); + void registerExtension(TweedExtension extension); } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/DefaultMiddlewareContainer.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/DefaultMiddlewareContainer.java index 01cd289..6b81855 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/DefaultMiddlewareContainer.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/DefaultMiddlewareContainer.java @@ -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 implements MiddlewareContainer { private static final String CONTAINER_ID = ""; + @Getter private List> middlewares = new ArrayList<>(); private final Set middlewareIds = new HashSet<>(); private boolean sealed = false; @@ -20,19 +22,37 @@ public class DefaultMiddlewareContainer implements MiddlewareContainer { } @Override - public void register(Middleware middleware) { - if (sealed) { - throw new IllegalStateException("Middleware container has already been sealed"); + public void registerAll(Collection> middlewares) { + requireUnsealed(); + + for (Middleware 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 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 diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/MiddlewareContainer.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/MiddlewareContainer.java index 6e4ebc5..49ab684 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/MiddlewareContainer.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/middleware/MiddlewareContainer.java @@ -1,6 +1,12 @@ package de.siphalor.tweed5.core.api.middleware; +import java.util.Collection; + public interface MiddlewareContainer extends Middleware { + default void registerAll(Collection> middlewares) { + middlewares.forEach(this::register); + } void register(Middleware middleware); void seal(); + Collection> middlewares(); } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValidationExtension.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValidationExtension.java deleted file mode 100644 index 56524d8..0000000 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValidationExtension.java +++ /dev/null @@ -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 validationMiddleware(ConfigEntry configEntry); -} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValidationMiddleware.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValidationMiddleware.java deleted file mode 100644 index 6e7b537..0000000 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValidationMiddleware.java +++ /dev/null @@ -1,8 +0,0 @@ -package de.siphalor.tweed5.core.api.validation; - -import de.siphalor.tweed5.core.api.entry.ConfigEntry; - -@FunctionalInterface -public interface ConfigEntryValidationMiddleware { - void validate(ConfigEntry configEntry, T value) throws ConfigEntryValueValidationException; -} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValueValidationException.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValueValidationException.java deleted file mode 100644 index 2c64609..0000000 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/validation/ConfigEntryValueValidationException.java +++ /dev/null @@ -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); - } -} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java index 181a87a..1b95795 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/DefaultConfigContainer.java @@ -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 implements ConfigContainer { @Getter private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP; - private final HashMap, TweedExtension> extensions = new HashMap<>(); + private final Map, TweedExtension> extensions = new HashMap<>(); private ConfigEntry rootEntry; private PatchworkClass entryExtensionsDataPatchworkClass; private Map, RegisteredExtensionDataImpl> registeredEntryDataExtensions; @@ -33,7 +34,11 @@ public class DefaultConfigContainer implements ConfigContainer { @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 implements ConfigContainer { requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP); registeredEntryDataExtensions = new HashMap<>(); + Collection additionalExtensions = new ArrayList<>(); TweedExtensionSetupContext extensionSetupContext = new TweedExtensionSetupContext() { @Override public ConfigContainer configContainer() { @@ -56,10 +62,26 @@ public class DefaultConfigContainer implements ConfigContainer { 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 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 entryExtensionsDataGenerator = PatchworkClassCreator.builder() diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/BaseConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/BaseConfigEntryImpl.java index 0fa075d..7cd8c14 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/BaseConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/BaseConfigEntryImpl.java @@ -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 implements ConfigEntry { - private static final ConfigEntryValidationMiddleware ROOT_VALIDATION = new ConfigEntryValidationMiddleware() { - @Override - public void validate(ConfigEntry configEntry, U value) {} - }; @NotNull private final Class 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 implements ConfigEntry { this.container = container; this.extensionsData = container.createExtensionsData(); - - MiddlewareContainer 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 implements ConfigEntry { 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); - } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CoherentCollectionConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CoherentCollectionConfigEntryImpl.java index 98a6871..e73193d 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CoherentCollectionConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CoherentCollectionConfigEntryImpl.java @@ -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> 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); + } + } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/ReflectiveCompoundConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/ReflectiveCompoundConfigEntryImpl.java index d9a6dcd..2e83f25 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/ReflectiveCompoundConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/ReflectiveCompoundConfigEntryImpl.java @@ -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 extends BaseConfigEntryImpl } 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 extends BaseConfigEntryImpl } } + @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; diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java index c7cc32d..aa0a35c 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/SimpleConfigEntryImpl.java @@ -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 extends BaseConfigEntryImpl implements SimpleConfigEntry { public SimpleConfigEntryImpl(Class valueClass) { super(valueClass); } + + @Override + public void visitInOrder(ConfigEntryVisitor visitor) { + visitor.visitEntry(this); + } + + @Override + public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { + visitor.visitEntry(this, value); + } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java index 09c65dd..1378581 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/StaticMapCompoundConfigEntryImpl.java @@ -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> 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) entry).visitInOrder(visitor, value.get(key)); + visitor.leaveCompoundSubEntry(key); + } + }); + } + visitor.leaveCompoundEntry(this, value); + } + } } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java index 76527e5..1285255 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/comment/impl/TweedEntryWriterCommentMiddleware.java @@ -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.middleware.Middleware; 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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,12 +22,12 @@ class TweedEntryWriterCommentMiddleware implements Middleware> innerCasted = (TweedEntryWriter>) inner; return (TweedEntryWriter>) (writer, value, entry, context) -> { - if (writer instanceof CompoundDataWriter) { + if (writer instanceof CompoundDataVisitor) { // Comment is already written in front of the key by the CompoundDataWriter, // so we don't have to write it here. // We also want to unwrap the original writer, // so that the special comment writing is limited to compounds. - writer = ((CompoundDataWriter) writer).delegate; + writer = ((CompoundDataVisitor) writer).delegate; } else { String comment = getEntryComment(entry); if (comment != null) { @@ -37,7 +37,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware) entry)), + new CompoundDataVisitor(writer, ((CompoundConfigEntry) entry)), value, entry, context @@ -49,8 +49,8 @@ class TweedEntryWriterCommentMiddleware implements Middleware compoundConfigEntry; @Override diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java new file mode 100644 index 0000000..eb4f840 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTracking.java @@ -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 contextStack = new ArrayDeque<>(50); + private final Deque pathParts = new ArrayDeque<>(50); + private final Deque 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, + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java new file mode 100644 index 0000000..e27b2f0 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryValueVisitor.java @@ -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 void visitEntry(ConfigEntry entry, T value) { + delegate.visitEntry(entry, value); + entryVisited(); + } + + @Override + public boolean enterCollectionEntry(ConfigEntry entry, T value) { + boolean enter = delegate.enterCollectionEntry(entry, value); + if (enter) { + pathTracking.pushListContext(); + } + return enter; + } + + @Override + public void leaveCollectionEntry(ConfigEntry entry, T value) { + delegate.leaveCollectionEntry(entry, value); + pathTracking.popContext(); + entryVisited(); + } + + @Override + public boolean enterCompoundEntry(ConfigEntry 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 void leaveCompoundEntry(ConfigEntry entry, T value) { + delegate.leaveCompoundEntry(entry, value); + pathTracking.popContext(); + entryVisited(); + } + + private void entryVisited() { + if (pathTracking.currentContext() == PathTracking.Context.LIST) { + pathTracking.incrementListIndex(); + } + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryVisitor.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryVisitor.java new file mode 100644 index 0000000..c0917a5 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingConfigEntryVisitor.java @@ -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(); + } + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataReader.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataReader.java new file mode 100644 index 0000000..01e0fed --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataReader.java @@ -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; + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataVisitor.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataVisitor.java new file mode 100644 index 0000000..641925c --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataVisitor.java @@ -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); + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherData.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherData.java new file mode 100644 index 0000000..4109113 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/PatherData.java @@ -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(); +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtension.java new file mode 100644 index 0000000..477631b --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtension.java @@ -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 rwContextPathTrackingData; + private Middleware> entryReaderMiddleware; + private Middleware> 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> createEntryReaderMiddleware() { + return new Middleware>() { + @Override + public String id() { + return PATHER_ID; + } + + @Override + public TweedEntryReader process(TweedEntryReader inner) { + //noinspection unchecked + TweedEntryReader> castedInner = (TweedEntryReader>) inner; + + return (TweedDataReader reader, ConfigEntry 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> createEntryWriterMiddleware() { + return new Middleware>() { + @Override + public String id() { + return PATHER_ID; + } + + @Override + public TweedEntryWriter process(TweedEntryWriter inner) { + //noinspection unchecked + TweedEntryWriter> castedInner = (TweedEntryWriter>) inner; + + return (TweedDataVisitor writer, Object value, ConfigEntry 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> entryReaderMiddleware() { + return entryReaderMiddleware; + } + + @Override + public @Nullable Middleware> entryWriterMiddleware() { + return entryWriterMiddleware; + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ConfigEntryValidator.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ConfigEntryValidator.java new file mode 100644 index 0000000..f226aa8 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ConfigEntryValidator.java @@ -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 { + ValidationResult validate(ConfigEntry configEntry, T value); + + @NotNull + String description(ConfigEntry configEntry); +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/EntrySpecificValidation.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/EntrySpecificValidation.java new file mode 100644 index 0000000..96d3972 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/EntrySpecificValidation.java @@ -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> validators(); +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java new file mode 100644 index 0000000..66f0369 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationExtension.java @@ -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 { + ValidationIssues validate(ConfigEntry entry, T value); +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationProvidingExtension.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationProvidingExtension.java new file mode 100644 index 0000000..302ec88 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/ValidationProvidingExtension.java @@ -0,0 +1,7 @@ +package de.siphalor.tweed5.defaultextensions.validation.api; + +import de.siphalor.tweed5.core.api.middleware.Middleware; + +public interface ValidationProvidingExtension { + Middleware validationMiddleware(); +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssue.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssue.java new file mode 100644 index 0000000..744590e --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssue.java @@ -0,0 +1,9 @@ +package de.siphalor.tweed5.defaultextensions.validation.api.result; + +import lombok.Value; + +@Value +public class ValidationIssue { + String message; + ValidationIssueLevel level; +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssueLevel.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssueLevel.java new file mode 100644 index 0000000..5bce8a4 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssueLevel.java @@ -0,0 +1,7 @@ +package de.siphalor.tweed5.defaultextensions.validation.api.result; + +public enum ValidationIssueLevel { + INFO, + WARN, + ERROR +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssues.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssues.java new file mode 100644 index 0000000..d591ea3 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationIssues.java @@ -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 issuesByPath(); + + @Value + class EntryIssues { + ConfigEntry entry; + Collection issues; + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java new file mode 100644 index 0000000..958562d --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/result/ValidationResult.java @@ -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 { + private final T value; + @NotNull + private final Collection issues; + private final boolean hasError; + + public static ValidationResult ok(T value) { + return new ValidationResult<>(value, Collections.emptyList(), false); + } + + public static ValidationResult withIssues(T value, @NotNull Collection issues) { + return new ValidationResult<>(value, issues, issuesContainError(issues)); + } + + private static boolean issuesContainError(Collection issues) { + if (issues.isEmpty()) { + return false; + } + for (ValidationIssue issue : issues) { + if (issue.level() == ValidationIssueLevel.ERROR) { + return true; + } + } + return false; + } + + public ValidationResult andThen(Function> function) { + if (hasError) { + return this; + } + + ValidationResult 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 combinedIssues = new ArrayList<>(issues.size() + functionResult.issues.size()); + combinedIssues.addAll(issues); + combinedIssues.addAll(functionResult.issues); + + return new ValidationResult<>(functionResult.value, combinedIssues, functionResult.hasError); + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/validators/NumberRangeValidator.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/validators/NumberRangeValidator.java new file mode 100644 index 0000000..2cb182f --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/validators/NumberRangeValidator.java @@ -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 implements ConfigEntryValidator { + @NotNull + Class numberClass; + @Nullable + N minimum; + @Nullable + N maximum; + + @Override + public ValidationResult validate(ConfigEntry 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 String description(ConfigEntry 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 + "."; + } + } + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/validators/SimpleValidatorMiddleware.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/validators/SimpleValidatorMiddleware.java new file mode 100644 index 0000000..14ec162 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/api/validators/SimpleValidatorMiddleware.java @@ -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 { + String id; + ConfigEntryValidator validator; + + @Override + public ConfigEntryValidator process(ConfigEntryValidator inner) { + return new ConfigEntryValidator() { + @Override + public ValidationResult validate(ConfigEntry configEntry, T value) { + return inner.validate(configEntry, value).andThen(v -> validator.validate(configEntry, v)); + } + + @Override + public @NotNull String description(ConfigEntry configEntry) { + String description = validator.description(configEntry); + if (description.isEmpty()) { + return inner.description(configEntry); + } + return inner.description(configEntry) + "\n" + description; + } + }; + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/InternalValidationEntryData.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/InternalValidationEntryData.java new file mode 100644 index 0000000..3caadf9 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/InternalValidationEntryData.java @@ -0,0 +1,7 @@ +package de.siphalor.tweed5.defaultextensions.validation.impl; + +import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator; + +public interface InternalValidationEntryData { + ConfigEntryValidator completeEntryValidator(); +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java new file mode 100644 index 0000000..fc3529a --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImpl.java @@ -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 ValidationResult validate(ConfigEntry configEntry, T value) { + if (value == null) { + //noinspection unchecked + return (ValidationResult) PRIMITIVE_IS_NULL_RESULT; + } + return ValidationResult.ok(value); + } + + @Override + public @NotNull String description(ConfigEntry configEntry) { + return "Value must not be null."; + } + }; + private static final ConfigEntryValidator NOOP_VALIDATOR = new ConfigEntryValidator() { + @Override + public ValidationResult validate(ConfigEntry configEntry, T value) { + return ValidationResult.ok(value); + } + + @Override + public @NotNull String description(ConfigEntry configEntry) { + return ""; + } + }; + + private RegisteredExtensionData validationEntryDataExtension; + private MiddlewareContainer entryValidatorMiddlewareContainer; + private EntryValidationReaderMiddleware readerMiddleware; + private RegisteredExtensionData 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 commentMiddleware() { + return new Middleware() { + @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 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> getEntrySpecificValidators(ConfigEntry configEntry) { + if (!configEntry.extensionsData().isPatchworkPartSet(EntrySpecificValidation.class)) { + return Collections.emptyList(); + } + return ((EntrySpecificValidation) configEntry.extensionsData()).validators(); + } + + @Override + public @Nullable Middleware> entryReaderMiddleware() { + return readerMiddleware; + } + + @Override + public ValidationIssues validate(ConfigEntry 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> { + @Override + public String id() { + return "validation"; + } + + @Override + public Set mustComeAfter() { + return Collections.singleton("pather"); + } + + @Override + public TweedEntryReader process(TweedEntryReader inner) { + //noinspection unchecked + TweedEntryReader> castedInner = (TweedEntryReader>) inner; + return (TweedDataReader reader, ConfigEntry 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 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 void visitEntry(ConfigEntry entry, T value) { + ValidationResult 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 boolean enterCollectionEntry(ConfigEntry entry, T value) { + return true; + } + + @Override + public void leaveCollectionEntry(ConfigEntry entry, T value) { + visitEntry(entry, value); + } + + @Override + public boolean enterCompoundEntry(ConfigEntry entry, T value) { + return true; + } + + @Override + public void leaveCompoundEntry(ConfigEntry entry, T value) { + visitEntry(entry, value); + } + } + + @Value + private static class ValidationIssuesImpl implements ValidationIssues { + Map issuesByPath = new HashMap<>(); + } +} diff --git a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java new file mode 100644 index 0000000..2106a8d --- /dev/null +++ b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/validation/impl/ValidationExtensionImplTest.java @@ -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> configContainer; + private CommentExtension commentExtension; + private ValidationExtension validationExtension; + private StaticMapCompoundConfigEntryImpl> rootEntry; + private SimpleConfigEntryImpl byteEntry; + private SimpleConfigEntryImpl intEntry; + private SimpleConfigEntryImpl 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>) (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 commentData = (RegisteredExtensionData) configContainer.entryDataExtensions().get(AComment.class); + commentData.set(intEntry.extensionsData(), new AComment() { + @Override + public Class annotationType() { + return null; + } + + @Override + public String value() { + return "This is the main comment!"; + } + }); + //noinspection unchecked + RegisteredExtensionData entrySpecificValidation = (RegisteredExtensionData) configContainer.entryDataExtensions().get(EntrySpecificValidation.class); + entrySpecificValidation.set(byteEntry.extensionsData(), new EntrySpecificValidation() { + @Override + public Collection> validators() { + return Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100))); + } + }); + entrySpecificValidation.set(intEntry.extensionsData(), new EntrySpecificValidation() { + @Override + public Collection> validators() { + return Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Integer.class, null, 123))); + } + }); + entrySpecificValidation.set(doubleEntry.extensionsData(), new EntrySpecificValidation() { + @Override + public Collection> 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 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 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"); + } + +} \ No newline at end of file diff --git a/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataWriter.java b/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataWriter.java deleted file mode 100644 index 0080f7c..0000000 --- a/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataWriter.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.siphalor.tweed5.dataapi.api; - -public interface TweedDataWriter extends TweedDataVisitor { - -} diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java index 0f0e45b..7dc3c3c 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/ReadWriteExtension.java @@ -4,12 +4,12 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData; import de.siphalor.tweed5.dataapi.api.TweedDataReader; -import de.siphalor.tweed5.dataapi.api.TweedDataWriter; +import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; public interface ReadWriteExtension extends TweedExtension { ReadWriteContextExtensionsData createReadWriteContextExtensionsData(); T read(TweedDataReader reader, ConfigEntry entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryReadException; - void write(TweedDataWriter writer, T value, ConfigEntry entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException; + void write(TweedDataVisitor writer, T value, ConfigEntry entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException; } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedEntryWriter.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedEntryWriter.java index b1f0006..4f30917 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedEntryWriter.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/api/TweedEntryWriter.java @@ -2,9 +2,9 @@ package de.siphalor.tweed5.data.extension.api; import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.dataapi.api.TweedDataWriteException; -import de.siphalor.tweed5.dataapi.api.TweedDataWriter; +import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; @FunctionalInterface public interface TweedEntryWriter> { - 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; } diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java index c9cf964..822b21e 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java @@ -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.TweedDataReader; 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.PatchworkClassCreator; import de.siphalor.tweed5.patchwork.impl.PatchworkClass; @@ -132,7 +132,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { } @Override - public void write(TweedDataWriter writer, T value, ConfigEntry entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException { + public void write(TweedDataVisitor writer, T value, ConfigEntry entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryWriteException { try { getWriterChain(entry).write(writer, value, entry, new TweedReadWriteContextImpl(contextExtensionsData)); } catch (TweedDataWriteException e) { diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java index 01ea5e6..efaa2d9 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java @@ -34,7 +34,7 @@ public class TweedEntryReaderWriterImpls { @RequiredArgsConstructor private static class PrimitiveReaderWriter implements TweedEntryReaderWriter> { private final Function readerCall; - private final BiConsumer writerCall; + private final BiConsumer writerCall; @Override public T read(TweedDataReader reader, ConfigEntry entry, TweedReadContext context) throws TweedDataReadException { @@ -42,7 +42,7 @@ public class TweedEntryReaderWriterImpls { } @Override - public void write(TweedDataWriter writer, T value, ConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { + public void write(TweedDataVisitor writer, T value, ConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { requireNonNullWriteValue(value); writerCall.accept(writer, value); } @@ -79,7 +79,7 @@ public class TweedEntryReaderWriterImpls { } @Override - public void write(TweedDataWriter writer, C value, CoherentCollectionConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { + public void write(TweedDataVisitor writer, C value, CoherentCollectionConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { requireNonNullWriteValue(value); if (value.isEmpty()) { @@ -126,7 +126,7 @@ public class TweedEntryReaderWriterImpls { } @Override - public void write(TweedDataWriter writer, T value, CompoundConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { + public void write(TweedDataVisitor writer, T value, CompoundConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { requireNonNullWriteValue(value); writer.visitMapStart(); @@ -178,7 +178,7 @@ public class TweedEntryReaderWriterImpls { } @Override - public void write(TweedDataWriter writer, Object value, ConfigEntry entry, TweedWriteContext context) throws TweedDataWriteException { + public void write(TweedDataVisitor writer, Object value, ConfigEntry entry, TweedWriteContext context) throws TweedDataWriteException { writer.visitNull(); } diff --git a/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java b/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java index abc8292..d1e04b0 100644 --- a/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java +++ b/tweed5-serde-extension/src/test/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImplTest.java @@ -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.HjsonReader; 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 org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -96,7 +96,7 @@ class ReadWriteExtensionImplTest { assertEquals(Arrays.asList(true, false, true), result.get("list")); } - private TweedDataWriter setupWriter(Function writerFactory) { + private TweedDataVisitor setupWriter(Function writerFactory) { stringWriter = new StringWriter(); return writerFactory.apply(stringWriter); } diff --git a/tweed5-serde-hjson/src/main/java/de/siphalor/tweed5/data/hjson/HjsonWriter.java b/tweed5-serde-hjson/src/main/java/de/siphalor/tweed5/data/hjson/HjsonWriter.java index ee8b5ae..b74cdcf 100644 --- a/tweed5-serde-hjson/src/main/java/de/siphalor/tweed5/data/hjson/HjsonWriter.java +++ b/tweed5-serde-hjson/src/main/java/de/siphalor/tweed5/data/hjson/HjsonWriter.java @@ -1,7 +1,7 @@ package de.siphalor.tweed5.data.hjson; 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 org.jetbrains.annotations.NotNull; @@ -11,7 +11,7 @@ import java.util.*; import java.util.regex.Matcher; 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 Pattern LINE_FEED_PATTERN = Pattern.compile("\\n|\\r\\n");