diff --git a/src/main/java/net/woggioni/worth/serialization/ValueDumper.java b/src/main/java/net/woggioni/worth/serialization/ValueDumper.java index eec88d3..5256e38 100644 --- a/src/main/java/net/woggioni/worth/serialization/ValueDumper.java +++ b/src/main/java/net/woggioni/worth/serialization/ValueDumper.java @@ -18,6 +18,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import static net.woggioni.worth.utils.WorthUtils.tail; + public abstract class ValueDumper implements Dumper { protected final Value.Configuration cfg; @@ -30,10 +32,11 @@ public abstract class ValueDumper implements Dumper { protected Map getIdMap(Value root) { Map occurrencies = new HashMap<>(); - ValueVisitor visitor = new ValueVisitor(){ + ValueVisitor visitor = new ValueVisitor() { @Override - public boolean filter(Value value, TraversalContext ctx) { - if(value.type() == Value.Type.ARRAY || value.type() == Value.Type.OBJECT) { + public boolean visitPre(TraversalContext ctx) { + Value value = tail(ctx.getStack()).getValue(); + if (value.type() == Value.Type.ARRAY || value.type() == Value.Type.OBJECT) { ValueIdentity identity = new ValueIdentity(value); Integer i = occurrencies.getOrDefault(identity, 0); occurrencies.put(identity, ++i); @@ -46,8 +49,8 @@ public abstract class ValueDumper implements Dumper { ValueWalker.walk(root, visitor); Map result = new HashMap<>(); int i = 0; - for(Map.Entry entry : occurrencies.entrySet()) { - if(entry.getValue() > 1) { + for (Map.Entry entry : occurrencies.entrySet()) { + if (entry.getValue() > 1) { result.put(entry.getKey(), i++); } } diff --git a/src/main/java/net/woggioni/worth/traversal/AbstractStackElement.java b/src/main/java/net/woggioni/worth/traversal/AbstractStackElement.java new file mode 100644 index 0000000..15d1023 --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/AbstractStackElement.java @@ -0,0 +1,23 @@ +package net.woggioni.worth.traversal; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import net.woggioni.worth.xface.Value; + +@RequiredArgsConstructor +abstract class AbstractStackElement implements StackElement { + + @Getter + @Setter + private T context; + + private final Value value; + + boolean traverseChildren; + + @Override + public Value getValue() { + return value; + } +} diff --git a/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java b/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java index 41ad3b6..751459f 100644 --- a/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java +++ b/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java @@ -1,15 +1,15 @@ package net.woggioni.worth.traversal; import lombok.Getter; +import net.woggioni.worth.exception.NotImplementedException; import net.woggioni.worth.value.ArrayValue; import net.woggioni.worth.xface.Value; import java.util.Iterator; -public class ArrayStackElement extends StackElement { +import static net.woggioni.worth.utils.WorthUtils.newThrowable; - @Getter - private final ArrayValue value; +class ArrayStackElement extends AbstractStackElement { private final Iterator it; @@ -19,24 +19,28 @@ public class ArrayStackElement extends StackElement { private Value currentValue = null; ArrayStackElement(ArrayValue av) { - this.value = av; + super(av); it = av.iterator(); } - @Override - Value current() { - return currentValue; - } - - @Override Value next() { currentValue = it.next(); currentIndex++; return currentValue; } - @Override boolean hasNext() { return it.hasNext(); } + + @Override + public String getCurrentKey() { + throw newThrowable(NotImplementedException.class, + "currentKey not supported for value of type '%s'", getValue().type()); + } + + @Override + public int getCurrentIndex() { + return currentIndex; + } } diff --git a/src/main/java/net/woggioni/worth/traversal/LeafStackElement.java b/src/main/java/net/woggioni/worth/traversal/LeafStackElement.java new file mode 100644 index 0000000..a93e8df --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/LeafStackElement.java @@ -0,0 +1,24 @@ +package net.woggioni.worth.traversal; + +import net.woggioni.worth.exception.NotImplementedException; +import net.woggioni.worth.xface.Value; + +import static net.woggioni.worth.utils.WorthUtils.newThrowable; + +class LeafStackElement extends AbstractStackElement { + public LeafStackElement(Value value) { + super(value); + } + + @Override + public String getCurrentKey() { + throw newThrowable(NotImplementedException.class, + "currentKey not supported for value of type '%s'", getValue().type()); + } + + @Override + public int getCurrentIndex() { + throw newThrowable(NotImplementedException.class, + "currentIndex not supported for value of type '%s'", getValue().type()); + } +} diff --git a/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java b/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java index 25e1283..d3e7982 100644 --- a/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java +++ b/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java @@ -7,13 +7,10 @@ import net.woggioni.worth.xface.Value; import java.util.Iterator; import java.util.Map; -public class ObjectStackElement extends StackElement { +class ObjectStackElement extends AbstractStackElement { private final Iterator> it; - @Getter - private final ObjectValue value; - @Getter private int currentIndex; @@ -23,18 +20,12 @@ public class ObjectStackElement extends StackElement { private Value currentValue; public ObjectStackElement(ObjectValue ov) { - value = ov; + super(ov); it = ov.iterator(); currentIndex = -1; } - @Override - Value current() { - return currentValue; - } - - @Override - Value next() { + public Value next() { Map.Entry result = it.next(); currentKey = result.getKey(); currentIndex++; @@ -42,8 +33,17 @@ public class ObjectStackElement extends StackElement { return currentValue; } - @Override - boolean hasNext() { + public boolean hasNext() { return it.hasNext(); } + + @Override + public String getCurrentKey() { + return currentKey; + } + + @Override + public int getCurrentIndex() { + return currentIndex; + } } diff --git a/src/main/java/net/woggioni/worth/traversal/StackElement.java b/src/main/java/net/woggioni/worth/traversal/StackElement.java index d9c014b..6339354 100644 --- a/src/main/java/net/woggioni/worth/traversal/StackElement.java +++ b/src/main/java/net/woggioni/worth/traversal/StackElement.java @@ -2,8 +2,10 @@ package net.woggioni.worth.traversal; import net.woggioni.worth.xface.Value; -abstract class StackElement { - abstract Value current(); - abstract Value next(); - abstract boolean hasNext(); +public interface StackElement { + T getContext(); + void setContext(T ctx); + Value getValue(); + String getCurrentKey(); + int getCurrentIndex(); } diff --git a/src/main/java/net/woggioni/worth/traversal/TraversalContext.java b/src/main/java/net/woggioni/worth/traversal/TraversalContext.java index f340053..d952557 100644 --- a/src/main/java/net/woggioni/worth/traversal/TraversalContext.java +++ b/src/main/java/net/woggioni/worth/traversal/TraversalContext.java @@ -1,11 +1,8 @@ package net.woggioni.worth.traversal; -import net.woggioni.worth.xface.Value; - import java.util.List; -public interface TraversalContext { - Value getRoot(); - List getStack(); +public interface TraversalContext { + List> getStack(); String getPath(); } diff --git a/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java b/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java index 93e687c..c59590f 100644 --- a/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java +++ b/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java @@ -1,16 +1,6 @@ package net.woggioni.worth.traversal; -import net.woggioni.worth.value.*; -import net.woggioni.worth.xface.Value; - -public interface ValueVisitor { - - default void visit(ObjectValue value, TraversalContext ctx) {} - default void visit(ArrayValue value, TraversalContext ctx) {} - default void visit(BooleanValue value, TraversalContext ctx) {} - default void visit(StringValue value, TraversalContext ctx) {} - default void visit(IntegerValue value, TraversalContext ctx) {} - default void visit(FloatValue value, TraversalContext ctx) {} - default void visit(NullValue value, TraversalContext ctx) {} - default boolean filter(Value value, TraversalContext ctx) { return true; } +public interface ValueVisitor { + default boolean visitPre(TraversalContext ctx) { return true; } + default void visitPost(TraversalContext ctx) {} } \ No newline at end of file diff --git a/src/main/java/net/woggioni/worth/traversal/ValueWalker.java b/src/main/java/net/woggioni/worth/traversal/ValueWalker.java index 514604f..5c6c1fa 100644 --- a/src/main/java/net/woggioni/worth/traversal/ValueWalker.java +++ b/src/main/java/net/woggioni/worth/traversal/ValueWalker.java @@ -1,6 +1,7 @@ package net.woggioni.worth.traversal; -import net.woggioni.worth.value.*; +import net.woggioni.worth.value.ArrayValue; +import net.woggioni.worth.value.ObjectValue; import net.woggioni.worth.xface.Value; import java.util.ArrayList; @@ -10,6 +11,40 @@ import java.util.Optional; import java.util.function.Function; import static net.woggioni.worth.utils.WorthUtils.dynamicCast; +import static net.woggioni.worth.utils.WorthUtils.pop; +import static net.woggioni.worth.utils.WorthUtils.tail; + +class TraversalContextImpl implements TraversalContext { + private final List> immutableStack; + + public TraversalContextImpl(List> stack) { + immutableStack = Collections.unmodifiableList(stack); + } + + @Override + public List> getStack() { + return immutableStack; + } + + @Override + public String getPath() { + StringBuilder sb = new StringBuilder(); + for (StackElement se : immutableStack) { + ArrayStackElement ase; + ObjectStackElement ose; + if ((ase = dynamicCast(se, ArrayStackElement.class)) != null) { + sb.append("["); + sb.append(ase.getCurrentIndex()); + sb.append("]"); + } else if ((ose = dynamicCast(se, ObjectStackElement.class)) != null) { + sb.append("[\""); + sb.append(ose.getCurrentKey()); + sb.append("\"]"); + } + } + return sb.toString(); + } +} public class ValueWalker { @@ -20,7 +55,7 @@ public class ValueWalker { } public ValueWalker get(String key) { - if(parent.type() == Value.Type.OBJECT) { + if (parent.type() == Value.Type.OBJECT) { parent = parent.get(key); } else { parent = Value.Null; @@ -29,7 +64,7 @@ public class ValueWalker { } public ValueWalker get(int index) { - if(parent.type() == Value.Type.ARRAY) { + if (parent.type() == Value.Type.ARRAY) { parent = parent.get(index); } else { parent = Value.Null; @@ -42,7 +77,7 @@ public class ValueWalker { } public Optional map(Function callback) { - if(isPresent()) { + if (isPresent()) { return Optional.of(callback.apply(parent)); } else { return Optional.empty(); @@ -50,7 +85,7 @@ public class ValueWalker { } public Optional flatMap(Function> callback) { - if(isPresent()) { + if (isPresent()) { return callback.apply(parent); } else { return Optional.empty(); @@ -65,91 +100,56 @@ public class ValueWalker { return parent.type() == Value.Type.NULL; } - public static void walk(Value root, ValueVisitor visitor) { - List stack = new ArrayList<>(); - List immutableStack = Collections.unmodifiableList(stack); + private static AbstractStackElement stackElementFromValue(Value value) { + AbstractStackElement result; + switch (value.type()) { + case ARRAY: + result = new ArrayStackElement<>((ArrayValue) value); + break; + case OBJECT: + result = new ObjectStackElement<>((ObjectValue) value); + break; + default: + result = new LeafStackElement<>(value); + break; + } + return result; + } - TraversalContext ctx = new TraversalContext() { - - @Override - public Value getRoot() { - return root; + private static AbstractStackElement nextChildStackElement(StackElement parent) { + AbstractStackElement result = null; + if (parent instanceof ArrayStackElement) { + ArrayStackElement ase = (ArrayStackElement) parent; + if (ase.hasNext()) { + result = stackElementFromValue(ase.next()); } - - @Override - public List getStack() { - return immutableStack; + } else if (parent instanceof ObjectStackElement) { + ObjectStackElement ose = (ObjectStackElement) parent; + if (ose.hasNext()) { + result = stackElementFromValue(ose.next()); } + } + return result; + } - @Override - public String getPath() { - StringBuilder sb = new StringBuilder(); - for(StackElement se : stack) { - ArrayStackElement ase; - ObjectStackElement ose; - if((ase = dynamicCast(se, ArrayStackElement.class)) != null) { - sb.append("["); - sb.append(ase.getCurrentIndex()); - sb.append("]"); - } else if((ose = dynamicCast(se, ObjectStackElement.class)) != null) { - sb.append("[\""); - sb.append(ose.getCurrentKey()); - sb.append("\"]"); - } - } - return sb.toString(); - } - }; - - ObjectValue ov; - ArrayValue av = new ArrayValue(); - av.add(root); - ArrayStackElement ase = new ArrayStackElement(av); - stack.add(ase); - while(true) { - Value currentValue = stack.get(stack.size() - 1).next(); - if(visitor.filter(currentValue, ctx)) { - if ((av = dynamicCast(currentValue, ArrayValue.class)) != null) { - ase = new ArrayStackElement(av); - stack.add(ase); - } else if ((ov = dynamicCast(currentValue, ObjectValue.class)) != null) { - ObjectStackElement ose = new ObjectStackElement(ov); - stack.add(ose); - } else { - IntegerValue iv; - BooleanValue bv; - NullValue nv; - FloatValue fv; - StringValue sv; - if ((iv = dynamicCast(currentValue, IntegerValue.class)) != null) { - visitor.visit(iv, ctx); - } else if ((fv = dynamicCast(currentValue, FloatValue.class)) != null) { - visitor.visit(fv, ctx); - } else if ((bv = dynamicCast(currentValue, BooleanValue.class)) != null) { - visitor.visit(bv, ctx); - } else if ((sv = dynamicCast(currentValue, StringValue.class)) != null) { - visitor.visit(sv, ctx); - } else if ((nv = dynamicCast(currentValue, NullValue.class)) != null) { - visitor.visit(nv, ctx); - } - } - } - while(true) { - if(stack.size() == 1) return; - int lastIndex = stack.size() - 1; - StackElement se = stack.get(lastIndex); - if(!se.hasNext()) { - ObjectStackElement ose; - if((ase = dynamicCast(se, ArrayStackElement.class)) != null) { - visitor.visit(ase.getValue(), ctx); - } else if((ose = dynamicCast(se, ObjectStackElement.class)) != null) { - visitor.visit(ose.getValue(), ctx); - } - stack.remove(lastIndex); - } else { - break; + public static void walk(Value root, ValueVisitor visitor) { + List> stack = new ArrayList<>(); + TraversalContext ctx = new TraversalContextImpl<>(stack); + AbstractStackElement stackElement = stackElementFromValue(root); + stack.add(stackElement); + stackElement.traverseChildren = visitor.visitPre(ctx); + while (!stack.isEmpty()) { + AbstractStackElement last = tail(stack); + if(last.traverseChildren) { + AbstractStackElement childStackElement = nextChildStackElement(last); + if(childStackElement != null) { + stack.add(childStackElement); + childStackElement.traverseChildren = visitor.visitPre(ctx); + continue; } } + visitor.visitPost(ctx); + pop(stack); } } } diff --git a/src/main/java/net/woggioni/worth/utils/ListView.java b/src/main/java/net/woggioni/worth/utils/ListView.java new file mode 100644 index 0000000..41945fa --- /dev/null +++ b/src/main/java/net/woggioni/worth/utils/ListView.java @@ -0,0 +1,249 @@ +package net.woggioni.worth.utils; + +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; + +import static java.lang.Math.min; + +@RequiredArgsConstructor +public class ListView implements List { + private final List delegate; + private final int start; + private final int end; + + public ListView(List delegate, int start) { + this(delegate, start, -1); + } + + @Override + public int size() { + return end < 0 ? delegate.size() : min(end, delegate.size()) - start; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + Iterator it = iterator(); + while (it.hasNext()) { + if(Objects.equals(o, it.next())) { + return true; + } + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int index = start; + @Override + public boolean hasNext() { + return end < 0 ? index < size() : index < min(end, size()); + } + + @Override + public T next() { + return get(index++); + } + }; + } + + @Override + public Object[] toArray() { + int size = size(); + Object[] result = new Object[size]; + for(int i = 0; i < size; i++) { + result[i] = get(i); + } + return result; + } + + @Override + public T1[] toArray(T1[] t1s) { + int size = size(); + T1[] result = Arrays.copyOf(t1s, size); + for(int i = 0; i < size; i++) { + result[i] = (T1) get(i); + } + return result; + } + + @Override + public boolean add(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection collection) { + return false; + } + + @Override + public boolean addAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(int i, Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection collection) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public T get(int i) { + int index = start + i; + if(end >= 0 && index < end) { + throw new IndexOutOfBoundsException(i); + } + return delegate.get(start + i); + } + + @Override + public T set(int i, T t) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(int i, T t) { + throw new UnsupportedOperationException(); + } + + @Override + public T remove(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(Object o) { + int size = size(); + for(int i = 0; i < size; i++) { + if(Objects.equals(o, get(i))) { + return i; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object o) { + int size = size(); + for(int i = size - 1; i >= 0; i--) { + if(Objects.equals(o, get(i))) { + return i; + } + } + return -1; + } + + @Override + public ListIterator listIterator() { + return new ListViewIterator<>(this, 0); + } + + @Override + public ListIterator listIterator(int i) { + if(i < 0 || i > size()) { + throw new IndexOutOfBoundsException(0); + } else { + return new ListViewIterator<>(this, start); + } + } + + @Override + public List subList(int i, int i1) { + if(i < 0) { + throw new IndexOutOfBoundsException(0); + } else if(i1 > size()) { + throw new IndexOutOfBoundsException(i1); + } else { + return new ListView<>(delegate, start + i, start + i1); + } + } + + @RequiredArgsConstructor + private static class ListViewIterator implements ListIterator { + private final ListView listView; + int size; + int i; + + public ListViewIterator(ListView listView, int start) { + this.listView = listView; + size = listView.size(); + i = start; + } + + @Override + public boolean hasNext() { + return i < size; + } + + @Override + public T next() { + return listView.get(i++); + } + + @Override + public boolean hasPrevious() { + return i > 0; + } + + @Override + public T previous() { + return listView.get(--i); + } + + @Override + public int nextIndex() { + return i + 1; + } + + @Override + public int previousIndex() { + return i; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void set(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(T t) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/net/woggioni/worth/utils/WorthUtils.java b/src/main/java/net/woggioni/worth/utils/WorthUtils.java index 37da852..1fdce01 100644 --- a/src/main/java/net/woggioni/worth/utils/WorthUtils.java +++ b/src/main/java/net/woggioni/worth/utils/WorthUtils.java @@ -7,7 +7,9 @@ import java.io.*; import java.lang.reflect.Constructor; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -141,4 +143,16 @@ public class WorthUtils { System.setProperty(key, value); } } + + public static T tail(List l) { + return tail(l, -1); + } + + public static T tail(List l, int offset) { + return l.get(l.size() + offset); + } + + public static T pop(List l) { + return l.remove(l.size() - 1); + } } diff --git a/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java b/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java index 624daca..4ca2324 100644 --- a/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java +++ b/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java @@ -3,6 +3,7 @@ package net.woggioni.worth.traversal; import lombok.SneakyThrows; import net.woggioni.worth.serialization.json.JSONParser; import net.woggioni.worth.serialization.json.JSONTest; +import net.woggioni.worth.utils.Tuple2; import net.woggioni.worth.utils.WorthUtils; import net.woggioni.worth.value.*; import net.woggioni.worth.xface.Parser; @@ -33,65 +34,46 @@ public class ValueWalkerTest { Assert.assertEquals("Amazon", text.get()); } - private static class TestVisitor implements ValueVisitor { + private static class TestVisitor implements ValueVisitor { - public List values = new ArrayList<>(); + public List preValues = new ArrayList<>(); + public List postValues = new ArrayList<>(); @Override - public void visit(ObjectValue value, TraversalContext ctx) { - values.add(value); + public boolean visitPre(TraversalContext ctx) { + Value value = WorthUtils.tail(ctx.getStack()).getValue(); + preValues.add(value); + return true; } @Override - public void visit(ArrayValue value, TraversalContext ctx) { - values.add(value); - } - - @Override - public void visit(BooleanValue value, TraversalContext ctx) { - values.add(value); - } - - @Override - public void visit(StringValue value, TraversalContext ctx) { - values.add(value); - } - - @Override - public void visit(IntegerValue value, TraversalContext ctx) { - values.add(value); - } - - @Override - public void visit(FloatValue value, TraversalContext ctx) { - values.add(value); - } - - @Override - public void visit(NullValue value, TraversalContext ctx) { - values.add(value); + public void visitPost(TraversalContext ctx) { + Value value = WorthUtils.tail(ctx.getStack()).getValue(); + postValues.add(value); } } - private void walk(List result, Value value) { + private void walk(List preResult, List postResult, Value value) { ObjectValue ov; ArrayValue av; + preResult.add(value); if((av = WorthUtils.dynamicCast(value, ArrayValue.class)) != null) { for(Value v : av) { - walk(result, v); + walk(preResult, postResult, v); } } else if((ov = WorthUtils.dynamicCast(value, ObjectValue.class)) != null) { for(Map.Entry entry : ov) { - walk(result, entry.getValue()); + walk(preResult, postResult, entry.getValue()); } } - result.add(value); + postResult.add(value); } - private List recursiveWalk(Value root) { - List result = new ArrayList<>(); - walk(result, root); - return result; + private Tuple2, List> recursiveWalk(Value root) { + List preResult = new ArrayList<>(); + List postResult = new ArrayList<>(); + walk(preResult, postResult, root); + return new Tuple2<>(preResult, postResult); } @Test @@ -99,7 +81,8 @@ public class ValueWalkerTest { Value v = JSONParser.newInstance().parse(JSONTest.getTestSource("/test.json")); TestVisitor visitor = new TestVisitor(); ValueWalker.walk(v, visitor); - Assert.assertFalse(visitor.values.isEmpty()); - Assert.assertEquals(recursiveWalk(v), visitor.values); + Assert.assertFalse(visitor.preValues.isEmpty()); + Assert.assertFalse(visitor.postValues.isEmpty()); + Assert.assertEquals(recursiveWalk(v), new Tuple2<>(visitor.preValues, visitor.postValues)); } }