diff --git a/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/AttributesExtensionImpl.java b/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/AttributesExtensionImpl.java index 823e331..d5f92da 100644 --- a/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/AttributesExtensionImpl.java +++ b/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/AttributesExtensionImpl.java @@ -99,13 +99,7 @@ public class AttributesExtensionImpl implements AttributesExtension { ); @Override - public boolean enterCompoundEntry(ConfigEntry entry) { - enterEntry(entry); - return true; - } - - @Override - public boolean enterCollectionEntry(ConfigEntry entry) { + public boolean enterStructuredEntry(ConfigEntry entry) { enterEntry(entry); return true; } @@ -130,12 +124,7 @@ public class AttributesExtensionImpl implements AttributesExtension { } @Override - public void leaveCompoundEntry(ConfigEntry entry) { - defaults.pop(); - } - - @Override - public void leaveCollectionEntry(ConfigEntry entry) { + public void leaveStructuredEntry(ConfigEntry entry) { defaults.pop(); } diff --git a/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/serde/filter/AttributesReadWriteFilterExtensionImpl.java b/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/serde/filter/AttributesReadWriteFilterExtensionImpl.java index 12710c0..54b935a 100644 --- a/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/serde/filter/AttributesReadWriteFilterExtensionImpl.java +++ b/tweed5-attributes-extension/src/main/java/de/siphalor/tweed5/attributesextension/impl/serde/filter/AttributesReadWriteFilterExtensionImpl.java @@ -88,26 +88,14 @@ public class AttributesReadWriteFilterExtensionImpl } @Override - public boolean enterCollectionEntry(ConfigEntry entry) { + public boolean enterStructuredEntry(ConfigEntry entry) { attributesCollectors.push(new HashMap<>()); visitEntry(entry); return true; } @Override - public void leaveCollectionEntry(ConfigEntry entry) { - leaveContainerEntry(entry); - } - - @Override - public boolean enterCompoundEntry(ConfigEntry entry) { - attributesCollectors.push(new HashMap<>()); - visitEntry(entry); - return true; - } - - @Override - public void leaveCompoundEntry(ConfigEntry entry) { + public void leaveStructuredEntry(ConfigEntry entry) { leaveContainerEntry(entry); } diff --git a/tweed5-comment-loader-extension/src/main/java/de/siphalor/tweed5/commentloaderextension/impl/CommentLoaderExtensionImpl.java b/tweed5-comment-loader-extension/src/main/java/de/siphalor/tweed5/commentloaderextension/impl/CommentLoaderExtensionImpl.java index 07d5b8d..926d435 100644 --- a/tweed5-comment-loader-extension/src/main/java/de/siphalor/tweed5/commentloaderextension/impl/CommentLoaderExtensionImpl.java +++ b/tweed5-comment-loader-extension/src/main/java/de/siphalor/tweed5/commentloaderextension/impl/CommentLoaderExtensionImpl.java @@ -93,7 +93,7 @@ public class CommentLoaderExtensionImpl implements CommentLoaderExtension, Comme } Map commentsByKey = collectingCommentsVisitor.commentsByKey(); - PathTracking pathTracking = new PathTracking(); + PathTracking pathTracking = PathTracking.create(); configContainer.rootEntry().visitInOrder(new PathTrackingConfigEntryVisitor( entry -> { String key = pathTracking.currentPath(); diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java index 5b4914e..4d63e4f 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CollectionConfigEntry.java @@ -1,15 +1,44 @@ package de.siphalor.tweed5.core.api.entry; +import org.jspecify.annotations.Nullable; + import java.util.Collection; +import java.util.Collections; +import java.util.Map; import java.util.function.Consumer; -public interface CollectionConfigEntry> extends ConfigEntry { +public interface CollectionConfigEntry> extends StructuredConfigEntry { @Override default CollectionConfigEntry apply(Consumer> function) { - ConfigEntry.super.apply(function); + StructuredConfigEntry.super.apply(function); return this; } + @Override + default void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) { + if (value == null) { + return; + } + + if (visitor.enterStructuredEntry(this, value)) { + int index = 0; + for (E item : value) { + String indexString = Integer.toString(index); + if (visitor.enterStructuredSubEntry("element", indexString)) { + elementEntry().visitInOrder(visitor, item); + visitor.leaveStructuredSubEntry("element", indexString); + } + index++; + } + visitor.leaveStructuredEntry(this, value); + } + } + + @Override + default Map> subEntries() { + return Collections.singletonMap("element", elementEntry()); + } + ConfigEntry elementEntry(); T instantiateCollection(int size); diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java index c027ca9..d73ea58 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/CompoundConfigEntry.java @@ -1,17 +1,14 @@ package de.siphalor.tweed5.core.api.entry; -import java.util.Map; import java.util.function.Consumer; -public interface CompoundConfigEntry extends ConfigEntry { +public interface CompoundConfigEntry extends StructuredConfigEntry { @Override default CompoundConfigEntry apply(Consumer> function) { - ConfigEntry.super.apply(function); + StructuredConfigEntry.super.apply(function); return this; } - Map> subEntries(); - void set(T compoundValue, String key, V value); V get(T compoundValue, String key); 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 index 3c7d050..ac9b63b 100644 --- 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 @@ -3,26 +3,18 @@ package de.siphalor.tweed5.core.api.entry; public interface ConfigEntryValueVisitor { void visitEntry(ConfigEntry entry, T value); - default boolean enterCollectionEntry(ConfigEntry entry, T value) { + default boolean enterStructuredEntry(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); + default boolean enterStructuredSubEntry(String entryKey, String valueKey) { return true; } - default boolean enterCompoundSubEntry(String key) { - return true; + default void leaveStructuredSubEntry(String entryKey, String valueKey) { } - default void leaveCompoundSubEntry(String key) { - } - - default void leaveCompoundEntry(ConfigEntry entry, T value) { + default void leaveStructuredEntry(ConfigEntry entry, T value) { } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryVisitor.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryVisitor.java index af30717..7a58d06 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryVisitor.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/ConfigEntryVisitor.java @@ -3,26 +3,18 @@ package de.siphalor.tweed5.core.api.entry; public interface ConfigEntryVisitor { void visitEntry(ConfigEntry entry); - default boolean enterCollectionEntry(ConfigEntry entry) { + default boolean enterStructuredEntry(ConfigEntry entry) { visitEntry(entry); return true; } - default void leaveCollectionEntry(ConfigEntry entry) { - } - - default boolean enterCompoundEntry(ConfigEntry entry) { - visitEntry(entry); + default boolean enterStructuredSubEntry(String key) { return true; } - default boolean enterCompoundSubEntry(String key) { - return true; + default void leaveStructuredSubEntry(String key) { } - default void leaveCompoundSubEntry(String key) { - } - - default void leaveCompoundEntry(ConfigEntry entry) { + default void leaveStructuredEntry(ConfigEntry entry) { } } diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/StructuredConfigEntry.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/StructuredConfigEntry.java new file mode 100644 index 0000000..9d66aa4 --- /dev/null +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/api/entry/StructuredConfigEntry.java @@ -0,0 +1,27 @@ +package de.siphalor.tweed5.core.api.entry; + +import java.util.Map; +import java.util.function.Consumer; + +public interface StructuredConfigEntry extends ConfigEntry { + @Override + default StructuredConfigEntry apply(Consumer> function) { + ConfigEntry.super.apply(function); + return this; + } + + @Override + default void visitInOrder(ConfigEntryVisitor visitor) { + if (visitor.enterStructuredEntry(this)) { + subEntries().forEach((key, entry) -> { + if (visitor.enterStructuredSubEntry(key)) { + entry.visitInOrder(visitor); + visitor.leaveStructuredSubEntry(key); + } + }); + visitor.leaveStructuredEntry(this); + } + } + + Map> subEntries(); +} diff --git a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CollectionConfigEntryImpl.java b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CollectionConfigEntryImpl.java index 20c8992..c15d858 100644 --- a/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CollectionConfigEntryImpl.java +++ b/tweed5-core/src/main/java/de/siphalor/tweed5/core/impl/entry/CollectionConfigEntryImpl.java @@ -31,26 +31,6 @@ public class CollectionConfigEntryImpl> extends BaseC return collectionConstructor.apply(size); } - @Override - public void visitInOrder(ConfigEntryVisitor visitor) { - if (visitor.enterCollectionEntry(this)) { - elementEntry.visitInOrder(visitor); - 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); - } - } - @Override public T deepCopy(T value) { T copy = collectionConstructor.apply(value.size()); 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 d233fe1..519003c 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 @@ -51,32 +51,19 @@ public class StaticMapCompoundConfigEntryImpl> ext return mapConstructor.apply(compoundEntries.size()); } - @Override - public void visitInOrder(ConfigEntryVisitor visitor) { - if (visitor.enterCompoundEntry(this)) { - compoundEntries.forEach((key, entry) -> { - if (visitor.enterCompoundSubEntry(key)) { - entry.visitInOrder(visitor); - visitor.leaveCompoundSubEntry(key); - } - }); - visitor.leaveCompoundEntry(this); - } - } - @Override public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { - if (visitor.enterCompoundEntry(this, value)) { + if (visitor.enterStructuredEntry(this, value)) { if (value != null) { compoundEntries.forEach((key, entry) -> { - if (visitor.enterCompoundSubEntry(key)) { + if (visitor.enterStructuredSubEntry(key, key)) { //noinspection unchecked ((ConfigEntry) entry).visitInOrder(visitor, value.get(key)); - visitor.leaveCompoundSubEntry(key); + visitor.leaveStructuredSubEntry(key, key); } }); } - visitor.leaveCompoundEntry(this, value); + visitor.leaveStructuredEntry(this, value); } } 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 index 40b1410..f07ba0d 100644 --- 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 @@ -1,62 +1,15 @@ package de.siphalor.tweed5.defaultextensions.pather.api; -import org.jspecify.annotations.Nullable; +import de.siphalor.tweed5.defaultextensions.pather.impl.PathTrackingImpl; -import java.util.ArrayDeque; -import java.util.Deque; - -public class PathTracking { - 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 @Nullable Context currentContext() { - return contextStack.peek(); +public interface PathTracking { + static PathTracking create() { + return new PathTrackingImpl(); } - public void popContext() { - if (contextStack.pop() == Context.LIST) { - listIndexes.pop(); - popPathPart(); - } - } + void pushPathPart(String pathPart); - public void pushMapContext() { - contextStack.push(Context.MAP); - } + void popPathPart(); - public void pushPathPart(String part) { - pathParts.push(part); - pathBuilder.append(".").append(part); - } - - public void popPathPart() { - if (!pathParts.isEmpty()) { - 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; - } - - public String currentPath() { - return pathBuilder.toString(); - } - - public enum Context { - LIST, MAP, - } + String currentPath(); } 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 index c74b22d..e154b68 100644 --- 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 @@ -8,64 +8,35 @@ import org.jspecify.annotations.Nullable; @RequiredArgsConstructor public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor { private final ConfigEntryValueVisitor delegate; - private final PathTracking pathTracking; + private final ValuePathTracking 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); + public boolean enterStructuredEntry(ConfigEntry entry, T value) { + return delegate.enterStructuredEntry(entry, value); + } + + @Override + public boolean enterStructuredSubEntry(String entryKey, String valueKey) { + boolean enter = delegate.enterStructuredSubEntry(entryKey, valueKey); if (enter) { - pathTracking.pushListContext(); + pathTracking.pushPathPart(entryKey, valueKey); } 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); + public void leaveStructuredSubEntry(String entryKey, String valueKey) { + delegate.leaveStructuredSubEntry(entryKey, valueKey); 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(); - } + public void leaveStructuredEntry(ConfigEntry entry, T value) { + delegate.leaveStructuredEntry(entry, value); } } 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 index c0917a5..126a3c8 100644 --- 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 @@ -12,37 +12,16 @@ public class PathTrackingConfigEntryVisitor implements ConfigEntryVisitor { @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; + public boolean enterStructuredEntry(ConfigEntry entry) { + return delegate.enterStructuredEntry(entry); } @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); + public boolean enterStructuredSubEntry(String key) { + boolean enter = delegate.enterStructuredSubEntry(key); if (enter) { pathTracking.pushPathPart(key); } @@ -50,21 +29,13 @@ public class PathTrackingConfigEntryVisitor implements ConfigEntryVisitor { } @Override - public void leaveCompoundSubEntry(String key) { - delegate.leaveCompoundSubEntry(key); + public void leaveStructuredSubEntry(String key) { + delegate.leaveStructuredSubEntry(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(); - } + public void leaveStructuredEntry(ConfigEntry entry) { + delegate.leaveStructuredEntry(entry); } } 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 index c3e20ae..2603f86 100644 --- 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 @@ -5,10 +5,14 @@ import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataToken; import lombok.RequiredArgsConstructor; +import java.util.ArrayDeque; + @RequiredArgsConstructor public class PathTrackingDataReader implements TweedDataReader { private final TweedDataReader delegate; private final PathTracking pathTracking; + private final ArrayDeque contextStack = new ArrayDeque<>(50); + private final ArrayDeque listIndexStack = new ArrayDeque<>(50); @Override public TweedDataToken peekToken() throws TweedDataReadException { @@ -18,21 +22,35 @@ public class PathTrackingDataReader implements TweedDataReader { @Override public TweedDataToken readToken() throws TweedDataReadException { TweedDataToken token = delegate.readToken(); + if (token.isListValue()) { + if (contextStack.peek() == Context.LIST) { + int index = listIndexStack.pop() + 1; + if (index != 0) { + pathTracking.popPathPart(); + } + pathTracking.pushPathPart(Integer.toString(index)); + listIndexStack.push(index); + } + } + if (token.isListStart()) { - pathTracking.pushListContext(); - } else if (token.isListValue()) { - pathTracking.incrementListIndex(); + contextStack.push(Context.LIST); + listIndexStack.push(-1); } else if (token.isListEnd()) { - pathTracking.popContext(); + contextStack.pop(); + int lastIndex = listIndexStack.pop(); + if (lastIndex >= 0) { + pathTracking.popPathPart(); + } } else if (token.isMapStart()) { - pathTracking.pushMapContext(); + contextStack.push(Context.MAP); pathTracking.pushPathPart("$"); } else if (token.isMapEntryKey()) { pathTracking.popPathPart(); pathTracking.pushPathPart(token.readAsString()); } else if (token.isMapEnd()) { pathTracking.popPathPart(); - pathTracking.popContext(); + contextStack.pop(); } return token; } @@ -41,4 +59,8 @@ public class PathTrackingDataReader implements TweedDataReader { public void close() throws Exception { delegate.close(); } + + private enum Context { + LIST, MAP, + } } 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 index 934f2db..329a361 100644 --- 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 @@ -6,10 +6,14 @@ import de.siphalor.tweed5.dataapi.api.decoration.TweedDataDecoration; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; +import java.util.ArrayDeque; + @RequiredArgsConstructor public class PathTrackingDataVisitor implements TweedDataVisitor { private final TweedDataVisitor delegate; private final PathTracking pathTracking; + private final ArrayDeque contextStack = new ArrayDeque<>(50); + private final ArrayDeque listIndexStack = new ArrayDeque<>(50); @Override public void visitNull() { @@ -72,42 +76,50 @@ public class PathTrackingDataVisitor implements TweedDataVisitor { } private void valueVisited() { - if (pathTracking.currentContext() == PathTracking.Context.LIST) { - pathTracking.incrementListIndex(); - } else { + Context context = contextStack.peek(); + if (context == Context.MAP_ENTRY) { + contextStack.pop(); pathTracking.popPathPart(); + } else if (context == Context.LIST) { + pathTracking.popPathPart(); + int index = listIndexStack.pop(); + listIndexStack.push(index + 1); + pathTracking.pushPathPart(Integer.toString(index)); } } @Override public void visitListStart() { delegate.visitListStart(); - pathTracking.pushListContext(); + contextStack.push(Context.LIST); + listIndexStack.push(0); + pathTracking.pushPathPart("0"); } @Override public void visitListEnd() { delegate.visitListEnd(); - pathTracking.popContext(); + contextStack.pop(); + listIndexStack.pop(); + pathTracking.popPathPart(); valueVisited(); } @Override public void visitMapStart() { delegate.visitMapStart(); - pathTracking.pushMapContext(); } @Override public void visitMapEntryKey(String key) { delegate.visitMapEntryKey(key); pathTracking.pushPathPart(key); + contextStack.push(Context.MAP_ENTRY); } @Override public void visitMapEnd() { delegate.visitMapEnd(); - pathTracking.popContext(); valueVisited(); } @@ -115,4 +127,8 @@ public class PathTrackingDataVisitor implements TweedDataVisitor { public void visitDecoration(TweedDataDecoration decoration) { delegate.visitDecoration(decoration); } + + private enum Context { + LIST, MAP_ENTRY, + } } diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java new file mode 100644 index 0000000..7f1f9b2 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/api/ValuePathTracking.java @@ -0,0 +1,34 @@ +package de.siphalor.tweed5.defaultextensions.pather.api; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor(staticName = "create") +public final class ValuePathTracking implements PathTracking { + private final PathTracking entryPathTracking = PathTracking.create(); + private final PathTracking valuePathTracking = PathTracking.create(); + + @Override + public void pushPathPart(String pathPart) { + this.pushPathPart(pathPart, pathPart); + } + + public void pushPathPart(String entryPathPart, String valuePathPart) { + entryPathTracking.pushPathPart(entryPathPart); + valuePathTracking.pushPathPart(valuePathPart); + } + + @Override + public void popPathPart() { + valuePathTracking.popPathPart(); + entryPathTracking.popPathPart(); + } + + @Override + public String currentPath() { + return valuePathTracking.currentPath(); + } + + public String currentEntryPath() { + return entryPathTracking.currentPath(); + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java new file mode 100644 index 0000000..e0b1844 --- /dev/null +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PathTrackingImpl.java @@ -0,0 +1,30 @@ +package de.siphalor.tweed5.defaultextensions.pather.impl; + +import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class PathTrackingImpl implements PathTracking { + private final StringBuilder pathBuilder = new StringBuilder(256); + private final Deque pathParts = new ArrayDeque<>(50); + + @Override + public void pushPathPart(String entryPathPart) { + pathParts.push(entryPathPart); + pathBuilder.append(".").append(entryPathPart); + } + + @Override + public void popPathPart() { + if (!pathParts.isEmpty()) { + String poppedPart = pathParts.pop(); + pathBuilder.setLength(pathBuilder.length() - poppedPart.length() - 1); + } + } + + @Override + public String currentPath() { + return pathBuilder.toString(); + } +} diff --git a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java index 20a7436..5cbf709 100644 --- a/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java +++ b/tweed5-default-extensions/src/main/java/de/siphalor/tweed5/defaultextensions/pather/impl/PatherExtensionImpl.java @@ -6,7 +6,6 @@ import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.data.extension.api.*; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension; -import de.siphalor.tweed5.dataapi.api.DelegatingTweedDataWriter; import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking; @@ -71,7 +70,7 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt return castedInner.read(reader, entry, context); } - pathTracking = new PathTracking(); + pathTracking = PathTracking.create(); context.extensionsData().set(rwContextPathTrackingAccess, pathTracking); try { return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context); @@ -80,7 +79,7 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt if (exceptionPathTracking != null) { throw new TweedEntryReadException( "Exception while reading entry at " - + String.join("/", exceptionPathTracking.currentPath()) + + exceptionPathTracking.currentPath() + ": " + e.getMessage(), e ); @@ -114,7 +113,7 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt return; } - pathTracking = new PathTracking(); + pathTracking = PathTracking.create(); context.extensionsData().set(rwContextPathTrackingAccess, pathTracking); try { castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context); @@ -123,7 +122,7 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt if (exceptionPathTracking != null) { throw new TweedEntryWriteException( "Exception while writing entry at " - + String.join("/", exceptionPathTracking.currentPath()) + + exceptionPathTracking.currentPath() + ": " + e.getMessage(), e ); 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 index 2446709..9667b41 100644 --- 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 @@ -20,6 +20,7 @@ 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.PatherExtension; +import de.siphalor.tweed5.defaultextensions.pather.api.ValuePathTracking; import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension; @@ -177,7 +178,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid @Override public ValidationIssues validate(ConfigEntry entry, @Nullable T value) { - PathTracking pathTracking = new PathTracking(); + ValuePathTracking pathTracking = ValuePathTracking.create(); ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking); entry.visitInOrder(new PathTrackingConfigEntryValueVisitor(validatingVisitor, pathTracking), value); @@ -281,22 +282,12 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid } @Override - public boolean enterCollectionEntry(ConfigEntry entry, T value) { + public boolean enterStructuredEntry(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) { + public void leaveStructuredEntry(ConfigEntry entry, T value) { visitEntry(entry, value); } } diff --git a/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataReaderTest.java b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataReaderTest.java new file mode 100644 index 0000000..0566cbf --- /dev/null +++ b/tweed5-default-extensions/src/test/java/de/siphalor/tweed5/defaultextensions/pather/api/PathTrackingDataReaderTest.java @@ -0,0 +1,72 @@ +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 de.siphalor.tweed5.dataapi.api.TweedDataTokens; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PathTrackingDataReaderTest { + @SneakyThrows + @Test + void test() { + PathTracking pathTracking = PathTracking.create(); + TweedDataReader mockedDelegate = mock(TweedDataReader.class); + + when(mockedDelegate.readToken()).thenReturn( + TweedDataTokens.getMapStart(), + TweedDataTokens.asMapEntryKey(new StringToken("key")), + TweedDataTokens.asMapEntryValue(new StringToken("value")), + TweedDataTokens.asMapEntryKey(new StringToken("list")), + TweedDataTokens.asMapEntryValue(TweedDataTokens.getListStart()), + TweedDataTokens.asListValue(new StringToken("first")), + TweedDataTokens.asListValue(TweedDataTokens.getMapStart()), + TweedDataTokens.asListValue(TweedDataTokens.getMapEnd()), + TweedDataTokens.getListEnd(), + TweedDataTokens.getMapEnd() + ); + + var reader = new PathTrackingDataReader(mockedDelegate, pathTracking); + + assertThat(reader.readToken()).isEqualTo(TweedDataTokens.getMapStart()); + assertThat(pathTracking.currentPath()).isEqualTo(".$"); + assertThat(reader.readToken().readAsString()).isEqualTo("key"); + assertThat(pathTracking.currentPath()).isEqualTo(".key"); + assertThat(reader.readToken().readAsString()).isEqualTo("value"); + assertThat(reader.readToken().readAsString()).isEqualTo("list"); + assertThat(pathTracking.currentPath()).isEqualTo(".list"); + assertThat(reader.readToken().isListStart()).isTrue(); + assertThat(pathTracking.currentPath()).isEqualTo(".list"); + assertThat(reader.readToken().readAsString()).isEqualTo("first"); + assertThat(reader.readToken().isMapStart()).isTrue(); + assertThat(pathTracking.currentPath()).startsWith(".list.1"); + assertThat(reader.readToken().isMapEnd()).isTrue(); + assertThat(reader.readToken().isListEnd()).isTrue(); + assertThat(pathTracking.currentPath()).isEqualTo(".list"); + assertThat(reader.readToken()).isEqualTo(TweedDataTokens.getMapEnd()); + assertThat(pathTracking.currentPath()).isEqualTo(""); + } + + @RequiredArgsConstructor + @EqualsAndHashCode + static class StringToken implements TweedDataToken { + private final String value; + + @Override + public boolean canReadAsString() { + return true; + } + + @Override + public String readAsString() { + return value; + } + } +} diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java index 919f194..1dd301a 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/CollectionConfigEntryImpl.java @@ -4,14 +4,11 @@ import de.siphalor.tweed5.construct.api.ConstructParameter; import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.entry.BaseConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry; -import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor; -import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor; import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCollectionConfigEntry; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; import org.jetbrains.annotations.NotNull; -import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.function.IntFunction; @@ -21,7 +18,7 @@ import java.util.function.IntFunction; @ToString(callSuper = true) public class CollectionConfigEntryImpl> extends BaseConfigEntry implements WeavableCollectionConfigEntry { private final IntFunction constructor; - private final @Nullable ConfigEntry elementEntry; + private final ConfigEntry elementEntry; public CollectionConfigEntryImpl( ConfigContainer configContainer, @@ -43,24 +40,6 @@ public class CollectionConfigEntryImpl> extends BaseC } } - @Override - public void visitInOrder(ConfigEntryVisitor visitor) { - if (visitor.enterCollectionEntry(this)) { - elementEntry.visitInOrder(visitor); - visitor.leaveCollectionEntry(this); - } - } - - @Override - public void visitInOrder(ConfigEntryValueVisitor visitor, T value) { - if (visitor.enterCollectionEntry(this, value)) { - for (E element : value) { - elementEntry.visitInOrder(visitor, element); - } - visitor.leaveCollectionEntry(this, value); - } - } - @Override public @NotNull T deepCopy(T value) { T copy = instantiateCollection(value.size()); diff --git a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java index cd912c4..fb17d1e 100644 --- a/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java +++ b/tweed5-weaver-pojo/src/main/java/de/siphalor/tweed5/weaver/pojo/impl/entry/StaticPojoCompoundConfigEntry.java @@ -86,22 +86,22 @@ public class StaticPojoCompoundConfigEntry extends BaseConfigEntry impleme @Override public void visitInOrder(ConfigEntryVisitor visitor) { - if (visitor.enterCompoundEntry(this)) { + if (visitor.enterStructuredEntry(this)) { subConfigEntries.forEach((key, entry) -> { - if (visitor.enterCompoundSubEntry(key)) { + if (visitor.enterStructuredSubEntry(key)) { entry.visitInOrder(visitor); - visitor.leaveCompoundSubEntry(key); + visitor.leaveStructuredSubEntry(key); } }); - visitor.leaveCompoundEntry(this); + visitor.leaveStructuredEntry(this); } } @Override public void visitInOrder(ConfigEntryValueVisitor visitor, @Nullable T value) { - if (visitor.enterCompoundEntry(this, value)) { + if (visitor.enterStructuredEntry(this, value)) { subEntries.forEach((key, entry) -> { - if (visitor.enterCompoundSubEntry(key)) { + if (visitor.enterStructuredSubEntry(key, key)) { try { Object subValue = entry.getter().invoke(value); //noinspection unchecked