[*] Generalize the entry system by introducing "structured entries"
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ public class CommentLoaderExtensionImpl implements CommentLoaderExtension, Comme
|
||||
}
|
||||
|
||||
Map<String, String> commentsByKey = collectingCommentsVisitor.commentsByKey();
|
||||
PathTracking pathTracking = new PathTracking();
|
||||
PathTracking pathTracking = PathTracking.create();
|
||||
configContainer.rootEntry().visitInOrder(new PathTrackingConfigEntryVisitor(
|
||||
entry -> {
|
||||
String key = pathTracking.currentPath();
|
||||
|
||||
@@ -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<E, T extends Collection<E>> extends ConfigEntry<T> {
|
||||
public interface CollectionConfigEntry<E, T extends Collection<E>> extends StructuredConfigEntry<T> {
|
||||
@Override
|
||||
default CollectionConfigEntry<E, T> apply(Consumer<ConfigEntry<T>> 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<String, ConfigEntry<?>> subEntries() {
|
||||
return Collections.singletonMap("element", elementEntry());
|
||||
}
|
||||
|
||||
ConfigEntry<E> elementEntry();
|
||||
|
||||
T instantiateCollection(int size);
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package de.siphalor.tweed5.core.api.entry;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface CompoundConfigEntry<T> extends ConfigEntry<T> {
|
||||
public interface CompoundConfigEntry<T> extends StructuredConfigEntry<T> {
|
||||
@Override
|
||||
default CompoundConfigEntry<T> apply(Consumer<ConfigEntry<T>> function) {
|
||||
ConfigEntry.super.apply(function);
|
||||
StructuredConfigEntry.super.apply(function);
|
||||
return this;
|
||||
}
|
||||
|
||||
Map<String, ConfigEntry<?>> subEntries();
|
||||
|
||||
<V> void set(T compoundValue, String key, V value);
|
||||
<V> V get(T compoundValue, String key);
|
||||
|
||||
|
||||
@@ -3,26 +3,18 @@ package de.siphalor.tweed5.core.api.entry;
|
||||
public interface ConfigEntryValueVisitor {
|
||||
<T> void visitEntry(ConfigEntry<T> entry, T value);
|
||||
|
||||
default <T> boolean enterCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||
default <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) {
|
||||
visitEntry(entry, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
default <T> void leaveCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||
}
|
||||
|
||||
default <T> boolean enterCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||
visitEntry(entry, value);
|
||||
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 <T> void leaveCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||
default <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.siphalor.tweed5.core.api.entry;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface StructuredConfigEntry<T> extends ConfigEntry<T> {
|
||||
@Override
|
||||
default StructuredConfigEntry<T> apply(Consumer<ConfigEntry<T>> 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<String, ConfigEntry<?>> subEntries();
|
||||
}
|
||||
@@ -31,26 +31,6 @@ public class CollectionConfigEntryImpl<E, T extends Collection<E>> 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());
|
||||
|
||||
@@ -51,32 +51,19 @@ public class StaticMapCompoundConfigEntryImpl<T extends Map<String, Object>> 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<Object>) entry).visitInOrder(visitor, value.get(key));
|
||||
visitor.leaveCompoundSubEntry(key);
|
||||
visitor.leaveStructuredSubEntry(key, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
visitor.leaveCompoundEntry(this, value);
|
||||
visitor.leaveStructuredEntry(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Context> contextStack = new ArrayDeque<>(50);
|
||||
private final Deque<String> pathParts = new ArrayDeque<>(50);
|
||||
private final Deque<Integer> 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();
|
||||
}
|
||||
|
||||
@@ -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 <T extends @Nullable Object> void visitEntry(ConfigEntry<T> entry, T value) {
|
||||
delegate.visitEntry(entry, value);
|
||||
entryVisited();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean enterCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||
boolean enter = delegate.enterCollectionEntry(entry, value);
|
||||
public <T> boolean enterStructuredEntry(ConfigEntry<T> 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 <T> void leaveCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||
delegate.leaveCollectionEntry(entry, value);
|
||||
pathTracking.popContext();
|
||||
entryVisited();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean enterCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||
boolean enter = delegate.enterCompoundEntry(entry, value);
|
||||
if (enter) {
|
||||
pathTracking.pushMapContext();
|
||||
}
|
||||
return enter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterCompoundSubEntry(String key) {
|
||||
boolean enter = delegate.enterCompoundSubEntry(key);
|
||||
if (enter) {
|
||||
pathTracking.pushPathPart(key);
|
||||
}
|
||||
return enter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveCompoundSubEntry(String key) {
|
||||
delegate.leaveCompoundSubEntry(key);
|
||||
public void leaveStructuredSubEntry(String entryKey, String valueKey) {
|
||||
delegate.leaveStructuredSubEntry(entryKey, valueKey);
|
||||
pathTracking.popPathPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void leaveCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||
delegate.leaveCompoundEntry(entry, value);
|
||||
pathTracking.popContext();
|
||||
entryVisited();
|
||||
}
|
||||
|
||||
private void entryVisited() {
|
||||
if (pathTracking.currentContext() == PathTracking.Context.LIST) {
|
||||
pathTracking.incrementListIndex();
|
||||
}
|
||||
public <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) {
|
||||
delegate.leaveStructuredEntry(entry, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Context> contextStack = new ArrayDeque<>(50);
|
||||
private final ArrayDeque<Integer> 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Context> contextStack = new ArrayDeque<>(50);
|
||||
private final ArrayDeque<Integer> 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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 <T> ValidationIssues validate(ConfigEntry<T> 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 <T> boolean enterCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||
public <T> boolean enterStructuredEntry(ConfigEntry<T> entry, T value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void leaveCollectionEntry(ConfigEntry<T> entry, T value) {
|
||||
visitEntry(entry, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean enterCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void leaveCompoundEntry(ConfigEntry<T> entry, T value) {
|
||||
public <T> void leaveStructuredEntry(ConfigEntry<T> entry, T value) {
|
||||
visitEntry(entry, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<E, T extends Collection<E>> extends BaseConfigEntry<T> implements WeavableCollectionConfigEntry<E, T> {
|
||||
private final IntFunction<T> constructor;
|
||||
private final @Nullable ConfigEntry<E> elementEntry;
|
||||
private final ConfigEntry<E> elementEntry;
|
||||
|
||||
public CollectionConfigEntryImpl(
|
||||
ConfigContainer<?> configContainer,
|
||||
@@ -43,24 +40,6 @@ public class CollectionConfigEntryImpl<E, T extends Collection<E>> 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());
|
||||
|
||||
@@ -86,22 +86,22 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> 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
|
||||
|
||||
Reference in New Issue
Block a user