From 562f02b607a5b2ff97d93859c33272a190b63124 Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Sat, 18 Dec 2021 03:19:18 +0100 Subject: [PATCH] Rework mutability of nodes --- .../java/io/xpipe/api/impl/DataTableImpl.java | 2 +- core/build.gradle | 2 + .../io/xpipe/core/data/DataStructureNode.java | 37 ++++- .../core/data/generic/GenericArrayReader.java | 6 +- .../GenericDataStructureNodeReader.java | 2 +- .../core/data/generic/GenericTupleReader.java | 2 +- .../io/xpipe/core/data/node/ArrayNode.java | 10 +- .../core/data/node/ImmutableValueNode.java | 26 ++++ .../core/data/node/MutableValueNode.java | 27 ++++ .../xpipe/core/data/node/NoKeyTupleNode.java | 10 +- .../xpipe/core/data/node/SimpleTupleNode.java | 11 +- .../io/xpipe/core/data/node/ValueNode.java | 69 +++++---- .../io/xpipe/core/data/type/ArrayType.java | 8 +- .../io/xpipe/core/data/type/TupleType.java | 2 +- .../io/xpipe/core/data/type/ValueType.java | 8 + .../io/xpipe/core/data/type/WildcardType.java | 8 + .../data/type/callback/DataTypeCallback.java | 2 +- .../data/typed/TypedDataStreamWriter.java | 2 +- .../typed/TypedDataStructureNodeReader.java | 97 ++++++------ .../TypedReusableDataStructureNodeReader.java | 26 ++-- .../io/xpipe/core/test/DataStructureTest.java | 114 +++++++++++++- .../xpipe/core/test/DataStructureTests.java | 143 +++++++++++++++++- .../core/test/GenericDataStructureIoTest.java | 35 ----- .../core/test/TypedDataStructureIoTest.java | 61 -------- core/src/test/java/module-info.java | 1 + 25 files changed, 503 insertions(+), 208 deletions(-) create mode 100644 core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java create mode 100644 core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java delete mode 100644 core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java delete mode 100644 core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java diff --git a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java index b49e2bce9..b8f9afbe1 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java @@ -99,7 +99,7 @@ public class DataTableImpl implements DataTable { }, false); } }.execute(); - return ArrayNode.wrap(nodes); + return ArrayNode.of(nodes); } @Override diff --git a/core/build.gradle b/core/build.gradle index 134d256c6..dd6288d47 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -20,6 +20,8 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.22' annotationProcessor 'org.projectlombok:lombok:1.18.22' + testCompileOnly 'org.projectlombok:lombok:1.18.22' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' diff --git a/core/src/main/java/io/xpipe/core/data/DataStructureNode.java b/core/src/main/java/io/xpipe/core/data/DataStructureNode.java index 54713a593..dc6cd2302 100644 --- a/core/src/main/java/io/xpipe/core/data/DataStructureNode.java +++ b/core/src/main/java/io/xpipe/core/data/DataStructureNode.java @@ -1,5 +1,8 @@ package io.xpipe.core.data; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.TupleNode; +import io.xpipe.core.data.node.ValueNode; import io.xpipe.core.data.type.DataType; import java.util.Iterator; @@ -29,6 +32,10 @@ public abstract class DataStructureNode implements Iterable { throw unsupported("set raw data"); } + public DataStructureNode setNull() { + throw unsupported("set null"); + } + public DataStructureNode set(int index, DataStructureNode node) { throw unsupported("set at index"); } @@ -47,6 +54,34 @@ public abstract class DataStructureNode implements Iterable { return false; } + public boolean isNull() { + throw unsupported("null check"); + } + + public final ValueNode asValue() { + if (!isValue()) { + throw new UnsupportedOperationException(getName() + " is not a value node"); + } + + return (ValueNode) this; + } + + public final TupleNode asTuple() { + if (!isTuple()) { + throw new UnsupportedOperationException(getName() + " is not a tuple node"); + } + + return (TupleNode) this; + } + + public final ArrayNode asArray() { + if (!isArray()) { + throw new UnsupportedOperationException(getName() + " is not an array node"); + } + + return (ArrayNode) this; + } + public DataStructureNode put(String keyName, DataStructureNode node) { throw unsupported("put node with key"); } @@ -67,7 +102,7 @@ public abstract class DataStructureNode implements Iterable { throw unsupported("size computation"); } - public abstract DataType getDataType(); + public abstract DataType determineDataType(); public DataStructureNode at(int index) { throw unsupported("integer indexing"); diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericArrayReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericArrayReader.java index cde4d694b..472c8f6ee 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericArrayReader.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericArrayReader.java @@ -92,7 +92,7 @@ public class GenericArrayReader implements GenericAbstractReader { throw new IllegalStateException("Array ended but is not full yet"); } - created = ArrayNode.wrap(nodes); + created = ArrayNode.of(nodes); } @Override @@ -142,7 +142,7 @@ public class GenericArrayReader implements GenericAbstractReader { throw new IllegalStateException("Array is full but got another value"); } - put(ValueNode.wrap(value)); + put(ValueNode.mutable(value)); } @Override @@ -156,6 +156,6 @@ public class GenericArrayReader implements GenericAbstractReader { throw new IllegalStateException(); } - return ArrayNode.wrap(nodes); + return ArrayNode.of(nodes); } } diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java index 12ee1aa99..aaebb6c42 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java @@ -84,6 +84,6 @@ public class GenericDataStructureNodeReader implements GenericDataStreamCallback return; } - node = ValueNode.wrap(value); + node = ValueNode.mutable(value); } } diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java index 796068329..97e2291d2 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java @@ -149,7 +149,7 @@ public class GenericTupleReader implements GenericAbstractReader { throw new IllegalStateException("Tuple is full but got another value"); } - putNode(ValueNode.wrap(value)); + putNode(ValueNode.mutable(value)); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java b/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java index 954499bb6..49b3c5800 100644 --- a/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java @@ -19,14 +19,14 @@ public class ArrayNode extends DataStructureNode { } public static ArrayNode of(DataStructureNode... dsn) { - return wrap(List.of(dsn)); + return of(List.of(dsn)); } - public static ArrayNode wrap(List valueNodes) { + public static ArrayNode of(List valueNodes) { return new ArrayNode(valueNodes); } - public static ArrayNode copy(List valueNodes) { + public static ArrayNode copyOf(List valueNodes) { return new ArrayNode(new ArrayList<>(valueNodes)); } @@ -69,8 +69,8 @@ public class ArrayNode extends DataStructureNode { } @Override - public ArrayType getDataType() { - return ArrayType.of(valueNodes.stream().map(DataStructureNode::getDataType).toList()); + public ArrayType determineDataType() { + return ArrayType.of(valueNodes.stream().map(DataStructureNode::determineDataType).toList()); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java b/core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java new file mode 100644 index 000000000..40d66b016 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java @@ -0,0 +1,26 @@ +package io.xpipe.core.data.node; + +import io.xpipe.core.data.DataStructureNode; + +public class ImmutableValueNode extends ValueNode { + + private final byte[] data; + + ImmutableValueNode(byte[] data) { + this.data = data; + } + + @Override + public String toString(int indent) { + return getClass().getSimpleName() + "(" + new String(data) + ")"; + } + + @Override + public DataStructureNode setRawData(byte[] data) { + throw new UnsupportedOperationException("Value node is immutable"); + } + + public byte[] getRawData() { + return data; + } +} diff --git a/core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java b/core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java new file mode 100644 index 000000000..dd84cd932 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java @@ -0,0 +1,27 @@ +package io.xpipe.core.data.node; + +import io.xpipe.core.data.DataStructureNode; + +public class MutableValueNode extends ValueNode { + + private byte[] data; + + MutableValueNode(byte[] data) { + this.data = data; + } + + @Override + public String toString(int indent) { + return getClass().getSimpleName() + "(" + new String(data) + ")"; + } + + @Override + public DataStructureNode setRawData(byte[] data) { + this.data = data; + return this; + } + + public byte[] getRawData() { + return data; + } +} diff --git a/core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java b/core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java index d80d67b9b..8672349d9 100644 --- a/core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java @@ -17,8 +17,14 @@ public class NoKeyTupleNode extends TupleNode { } @Override - public DataType getDataType() { - return TupleType.wrap(null, nodes.stream().map(DataStructureNode::getDataType).toList()); + public DataStructureNode set(int index, DataStructureNode node) { + nodes.set(index, node); + return this; + } + + @Override + public DataType determineDataType() { + return TupleType.of(null, nodes.stream().map(DataStructureNode::determineDataType).toList()); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java index 1c6433c54..cb6937b35 100644 --- a/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java @@ -19,8 +19,14 @@ public class SimpleTupleNode extends TupleNode { } @Override - public DataType getDataType() { - return TupleType.wrap(names, nodes.stream().map(DataStructureNode::getDataType).toList()); + public DataStructureNode set(int index, DataStructureNode node) { + nodes.set(index, node); + return this; + } + + @Override + public DataType determineDataType() { + return TupleType.of(names, nodes.stream().map(DataStructureNode::determineDataType).toList()); } @Override @@ -50,6 +56,7 @@ public class SimpleTupleNode extends TupleNode { @Override public DataStructureNode clear() { nodes.clear(); + names.clear(); return this; } diff --git a/core/src/main/java/io/xpipe/core/data/node/ValueNode.java b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java index ae283e6c1..39117482d 100644 --- a/core/src/main/java/io/xpipe/core/data/node/ValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java @@ -8,59 +8,68 @@ import lombok.EqualsAndHashCode; import java.nio.charset.StandardCharsets; @EqualsAndHashCode(callSuper = false) -public class ValueNode extends DataStructureNode { +public abstract class ValueNode extends DataStructureNode { - private byte[] data; + private static final byte[] NULL = new byte[] {0}; - private ValueNode(byte[] data) { - this.data = data; + public static ValueNode immutable(byte[] data) { + return new ImmutableValueNode(data); } - public static ValueNode wrap(byte[] data) { - return new ValueNode(data); + public static ValueNode mutableNull() { + return mutable(NULL); + } + + public static ValueNode nullValue() { + return mutable(NULL); + } + + public static ValueNode mutable(byte[] data) { + return new MutableValueNode(data); + } + + public static ValueNode mutable(Object o) { + return mutable(o.toString().getBytes(StandardCharsets.UTF_8)); + } + + public static ValueNode of(byte[] data) { + return mutable(data); } public static ValueNode of(Object o) { - return new ValueNode(o.toString().getBytes(StandardCharsets.UTF_8)); + return mutable(o); + } + + protected ValueNode() { } @Override - public DataStructureNode setRawData(byte[] data) { - this.data = data; - return this; - } + public abstract DataStructureNode setRawData(byte[] data); @Override - public boolean isValue() { - return true; - } - - @Override - public int asInt() { + public final int asInt() { return Integer.parseInt(asString()); } @Override - public String asString() { - return new String(data); + public final String asString() { + return new String(getRawData()); } @Override - protected String getName() { + public final boolean isValue() { + return true; + } + + @Override + protected final String getName() { return "value node"; } @Override - public String toString(int indent) { - return new String(data); + public final DataType determineDataType() { + return ValueType.of(); } - @Override - public DataType getDataType() { - return new ValueType(); - } - - public byte[] getRawData() { - return data; - } + public abstract byte[] getRawData(); } diff --git a/core/src/main/java/io/xpipe/core/data/type/ArrayType.java b/core/src/main/java/io/xpipe/core/data/type/ArrayType.java index a781b1460..872f345e0 100644 --- a/core/src/main/java/io/xpipe/core/data/type/ArrayType.java +++ b/core/src/main/java/io/xpipe/core/data/type/ArrayType.java @@ -11,14 +11,18 @@ import java.util.List; @EqualsAndHashCode public class ArrayType implements DataType { + public static ArrayType ofWildcard() { + return new ArrayType(WildcardType.of()); + } + public static ArrayType of(List types) { if (types.size() == 0) { - return new ArrayType(new WildcardType()); + return new ArrayType(WildcardType.of()); } var first = types.get(0); var eq = types.stream().allMatch(d -> d.equals(first)); - return new ArrayType(eq ? first : new WildcardType()); + return new ArrayType(eq ? first : WildcardType.of()); } private final DataType sharedType; diff --git a/core/src/main/java/io/xpipe/core/data/type/TupleType.java b/core/src/main/java/io/xpipe/core/data/type/TupleType.java index ff457f07a..63805393d 100644 --- a/core/src/main/java/io/xpipe/core/data/type/TupleType.java +++ b/core/src/main/java/io/xpipe/core/data/type/TupleType.java @@ -26,7 +26,7 @@ public class TupleType implements DataType { return new TupleType(List.of(), List.of()); } - public static TupleType wrap(List names, List types) { + public static TupleType of(List names, List types) { return new TupleType(names, types); } diff --git a/core/src/main/java/io/xpipe/core/data/type/ValueType.java b/core/src/main/java/io/xpipe/core/data/type/ValueType.java index 7904aad07..697920268 100644 --- a/core/src/main/java/io/xpipe/core/data/type/ValueType.java +++ b/core/src/main/java/io/xpipe/core/data/type/ValueType.java @@ -9,6 +9,14 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode public class ValueType implements DataType { + public static ValueType of() { + return new ValueType(); + } + + private ValueType() { + + } + @Override public String getName() { return "value"; diff --git a/core/src/main/java/io/xpipe/core/data/type/WildcardType.java b/core/src/main/java/io/xpipe/core/data/type/WildcardType.java index ffe7f978b..e3b6aaa63 100644 --- a/core/src/main/java/io/xpipe/core/data/type/WildcardType.java +++ b/core/src/main/java/io/xpipe/core/data/type/WildcardType.java @@ -5,6 +5,14 @@ import io.xpipe.core.data.type.callback.DataTypeCallback; public class WildcardType implements DataType { + public static WildcardType of() { + return new WildcardType(); + } + + private WildcardType() { + + } + @Override public String getName() { return "wildcard"; diff --git a/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallback.java b/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallback.java index 100876397..f9b3ed6c1 100644 --- a/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallback.java +++ b/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallback.java @@ -13,7 +13,7 @@ public interface DataTypeCallback { return new DataTypeCallback() { @Override public void onValue() { - typeConsumer.accept(new ValueType()); + typeConsumer.accept(ValueType.of()); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java index c0f776dce..d0d0b1b2f 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java @@ -50,7 +50,7 @@ public class TypedDataStreamWriter { if (!type.getTypes().get(i).isWildcard()) { write(out, tuple.at(i), type.getTypes().get(i)); } else { - GenericDataStreamWriter.write(out, tuple); + GenericDataStreamWriter.write(out, tuple.at(i)); } } } diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java index fc563d894..77ef2ea17 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java @@ -11,25 +11,35 @@ import io.xpipe.core.data.type.callback.DataTypeCallback; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Stack; public class TypedDataStructureNodeReader implements TypedAbstractReader { + public static TypedDataStructureNodeReader mutable(DataType type) { + return new TypedDataStructureNodeReader(type, false); + } + + public static TypedDataStructureNodeReader immutable(DataType type) { + return new TypedDataStructureNodeReader(type, true); + } + private int currentDataTypeIndex; private final List flattened; - private Stack> children; - private Stack nodes; + private final Stack> children; + private final Stack nodes; private DataStructureNode readNode; private boolean initialized; private int arrayDepth; - private boolean makeImmutable; + private final boolean makeImmutable; - public TypedDataStructureNodeReader(DataType type) { + private TypedDataStructureNodeReader(DataType type, boolean makeImmutable) { flattened = new ArrayList<>(); children = new Stack<>(); nodes = new Stack<>(); type.traverseType(DataTypeCallback.flatten(d -> flattened.add(d))); + this.makeImmutable = makeImmutable; } @Override @@ -61,55 +71,22 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { @Override public void onValue(byte[] data) { + var val = makeImmutable ? ValueNode.immutable(data) : ValueNode.mutable(data); if (!initialized) { - readNode = ValueNode.wrap(data); + readNode = val; return; } - children.peek().add(ValueNode.wrap(data)); + children.peek().add(val); if (!flattened.get(currentDataTypeIndex).isArray()) { currentDataTypeIndex++; } } - private void finishTuple() { - children.pop(); - var popped = nodes.pop(); - if (!popped.isTuple()) { - throw new IllegalStateException(); - } - - SimpleTupleNode tuple = (SimpleTupleNode) popped; - if (tuple.getNames().size() != tuple.getNodes().size()) { - throw new IllegalStateException(""); - } - - if (nodes.empty()) { - readNode = popped; - } else { - children.peek().add(popped); - } - } - private boolean isInArray() { return arrayDepth >= 1; } - private void finishArray() { - arrayDepth--; - if (!isInArray()) { - currentDataTypeIndex++; - } - - children.pop(); - var popped = nodes.pop(); - if (nodes.empty()) { - readNode = popped; - } else { - children.peek().add(popped); - } - } - @Override public void onGenericNode(DataStructureNode node) { children.peek().add(node); @@ -134,13 +111,32 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { var l = new ArrayList(tupleType.getSize()); children.push(l); - var newNode = TupleNode.wrapRaw(tupleType.getNames(), l); + + var tupleNames = makeImmutable ? + Collections.unmodifiableList(tupleType.getNames()) : new ArrayList<>(tupleType.getNames()); + var tupleNodes = makeImmutable ? Collections.unmodifiableList(l) : l; + var newNode = TupleNode.wrapRaw(tupleNames, tupleNodes); nodes.push(newNode); } @Override public void onTupleEnd() { - finishTuple(); + children.pop(); + var popped = nodes.pop(); + if (!popped.isTuple()) { + throw new IllegalStateException(); + } + + SimpleTupleNode tuple = (SimpleTupleNode) popped; + if (tuple.getNames().size() != tuple.getNodes().size()) { + throw new IllegalStateException(""); + } + + if (nodes.empty()) { + readNode = popped; + } else { + children.peek().add(popped); + } } @Override @@ -151,13 +147,26 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { var l = new ArrayList(); children.push(l); - var newNode = ArrayNode.wrap(l); + + var arrayNodes = makeImmutable ? Collections.unmodifiableList(l) : l; + var newNode = ArrayNode.of(arrayNodes); nodes.push(newNode); arrayDepth++; } @Override public void onArrayEnd() { - finishArray(); + arrayDepth--; + if (!isInArray()) { + currentDataTypeIndex++; + } + + children.pop(); + var popped = nodes.pop(); + if (nodes.empty()) { + readNode = popped; + } else { + children.peek().add(popped); + } } } diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedReusableDataStructureNodeReader.java b/core/src/main/java/io/xpipe/core/data/typed/TypedReusableDataStructureNodeReader.java index 2d940150e..0d1acf7e8 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedReusableDataStructureNodeReader.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedReusableDataStructureNodeReader.java @@ -11,7 +11,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Stack; -public class TypedReusableDataStructureNodeReader implements TypedDataStreamCallback { +public class TypedReusableDataStructureNodeReader implements TypedAbstractReader { private TypedDataStructureNodeReader initialReader; private DataStructureNode node; @@ -22,10 +22,15 @@ public class TypedReusableDataStructureNodeReader implements TypedDataStreamCall public TypedReusableDataStructureNodeReader(DataType type) { flattened = new ArrayList<>(); indices = new Stack<>(); - initialReader = new TypedDataStructureNodeReader(type); + initialReader = TypedDataStructureNodeReader.mutable(type); type.traverseType(DataTypeCallback.flatten(d -> flattened.add(d))); } + @Override + public boolean isDone() { + return true; + } + public DataStructureNode create() { return node; } @@ -47,10 +52,12 @@ public class TypedReusableDataStructureNodeReader implements TypedDataStreamCall } if (isInArray()) { - getCurrentParent().set(indices.peek(), ValueNode.wrap(data)); - indices.push(indices.pop() + 1); + getCurrentParent().set(indices.peek(), ValueNode.mutable(data)); } else { getCurrent().setRawData(data); + } + + if (!indices.isEmpty()) { indices.push(indices.pop() + 1); } } @@ -62,11 +69,8 @@ public class TypedReusableDataStructureNodeReader implements TypedDataStreamCall return; } - if (isInArray()) { - getCurrentParent().set(indices.peek(), node); - indices.push(indices.pop() + 1); - } else { - getCurrent().set(indices.peek(), node); + getCurrentParent().set(indices.peek(), node); + if (!indices.isEmpty()) { indices.push(indices.pop() + 1); } } @@ -140,7 +144,9 @@ public class TypedReusableDataStructureNodeReader implements TypedDataStreamCall indices.pop(); arrayDepth--; - indices.push(indices.pop() + 1); + if (!indices.isEmpty()) { + indices.push(indices.pop() + 1); + } } @Override diff --git a/core/src/test/java/io/xpipe/core/test/DataStructureTest.java b/core/src/test/java/io/xpipe/core/test/DataStructureTest.java index 43bec32ad..9b647143a 100644 --- a/core/src/test/java/io/xpipe/core/test/DataStructureTest.java +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTest.java @@ -1,22 +1,34 @@ package io.xpipe.core.test; import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.generic.GenericDataStreamParser; +import io.xpipe.core.data.generic.GenericDataStreamWriter; +import io.xpipe.core.data.generic.GenericDataStructureNodeReader; import io.xpipe.core.data.node.ArrayNode; import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.node.ValueNode; +import io.xpipe.core.data.typed.TypedDataStreamParser; +import io.xpipe.core.data.typed.TypedDataStreamWriter; +import io.xpipe.core.data.typed.TypedDataStructureNodeReader; +import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; public class DataStructureTest { public static DataStructureNode createTestData() { - var val = ValueNode.wrap("value".getBytes(StandardCharsets.UTF_8)); - var flatArray = ArrayNode.wrap(List.of(ValueNode.of(1), ValueNode.of(2))); + var val = ValueNode.of("value"); + var flatArray = ArrayNode.of(List.of(ValueNode.of(1), ValueNode.of(2))); var flatTuple = TupleNode.builder().add("key1", val).build(); - var nestedArray = ArrayNode.wrap(List.of(flatArray, flatTuple)); + var nestedArray = ArrayNode.of(List.of(flatArray, flatTuple)); return TupleNode.builder() .add("key1", val) .add("key2", flatArray) @@ -53,4 +65,100 @@ public class DataStructureTest { Assertions.assertEquals(key4.at(0), ArrayNode.of(ValueNode.of(1), ValueNode.of(2))); Assertions.assertEquals(key4.at(0).at(0).asInt(), 1); } + + @ParameterizedTest + @EnumSource(DataStructureTests.TypedDataset.class) + public void testTypes(DataStructureTests.TypedDataset ds) throws IOException { + for (var el : ds.nodes) { + Assertions.assertTrue(ds.type.matches(el)); + } + } + + @ParameterizedTest + @EnumSource(DataStructureTests.TypedDataset.class) + public void testGenericIo(DataStructureTests.TypedDataset ds) throws IOException { + for (var el : ds.nodes) { + var dataOut = new ByteArrayOutputStream(); + GenericDataStreamWriter.write(dataOut, el); + var data = dataOut.toByteArray(); + var reader = new GenericDataStructureNodeReader(); + GenericDataStreamParser.read(new ByteArrayInputStream(data), reader); + var readNode = reader.create(); + + Assertions.assertEquals(el, readNode); + } + } + + @ParameterizedTest + @EnumSource(DataStructureTests.TypedDataset.class) + public void testMutableTypedIo(DataStructureTests.TypedDataset ds) throws IOException { + for (var node : ds.nodes) { + var dataOut = new ByteArrayOutputStream(); + TypedDataStreamWriter.writeStructure(dataOut, node, ds.type); + var data = dataOut.toByteArray(); + + var reader = TypedDataStructureNodeReader.mutable(ds.type); + new TypedDataStreamParser(ds.type).readStructure(new ByteArrayInputStream(data), reader); + var readNode = reader.create(); + + Assertions.assertEquals(node, readNode); + Assertions.assertDoesNotThrow(() -> { + if (readNode.isTuple()) { + readNode.clear(); + Assertions.assertEquals(readNode.size(), 0); + } + + if (readNode.isArray()) { + readNode.clear(); + Assertions.assertEquals(readNode.size(), 0); + } + }); + } + } + + @ParameterizedTest + @EnumSource(DataStructureTests.TypedDataset.class) + public void testImmutableTypedIo(DataStructureTests.TypedDataset ds) throws IOException { + for (var node : ds.nodes) { + var dataOut = new ByteArrayOutputStream(); + TypedDataStreamWriter.writeStructure(dataOut, node, ds.type); + var data = dataOut.toByteArray(); + + var reader = TypedDataStructureNodeReader.immutable(ds.type); + new TypedDataStreamParser(ds.type).readStructure(new ByteArrayInputStream(data), reader); + var readNode = reader.create(); + + Assertions.assertEquals(node, readNode); + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + if (readNode.isTuple() || readNode.isArray()) { + readNode.clear(); + Assertions.assertEquals(readNode.size(), 0); + } else { + readNode.setRawData("abc".getBytes(StandardCharsets.UTF_8)); + } + }); + if (readNode.isTuple() || readNode.isArray()) { + Assertions.assertEquals(readNode.size(), node.size()); + } + } + } + + @ParameterizedTest + @EnumSource(DataStructureTests.TypedDataset.class) + public void testReusableTypedIo(DataStructureTests.TypedDataset ds) throws IOException { + var dataOut = new ByteArrayOutputStream(); + for (var node : ds.nodes) { + TypedDataStreamWriter.writeStructure(dataOut, node, ds.type); + } + + var data = dataOut.toByteArray(); + var in = new ByteArrayInputStream(data); + var reader = new TypedReusableDataStructureNodeReader(ds.type); + + for (var node : ds.nodes) { + new TypedDataStreamParser(ds.type).readStructure(in, reader); + var readNode = reader.create(); + Assertions.assertEquals(node, readNode); + } + } } diff --git a/core/src/test/java/io/xpipe/core/test/DataStructureTests.java b/core/src/test/java/io/xpipe/core/test/DataStructureTests.java index 8c17a82ac..8b59d0505 100644 --- a/core/src/test/java/io/xpipe/core/test/DataStructureTests.java +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTests.java @@ -4,17 +4,47 @@ import io.xpipe.core.data.DataStructureNode; import io.xpipe.core.data.node.ArrayNode; import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.node.ValueNode; +import io.xpipe.core.data.type.*; +import lombok.AllArgsConstructor; import java.nio.charset.StandardCharsets; import java.util.List; public class DataStructureTests { - public static DataStructureNode createTestData() { - var val = ValueNode.wrap("value".getBytes(StandardCharsets.UTF_8)); - var flatArray = ArrayNode.wrap(List.of(ValueNode.of(1), ValueNode.of(2))); + @AllArgsConstructor + + public static enum TypedDataset { + + // Variety + DATA_1(createTestDataType1(), List.of(createTestData11(), createTestData12())), + + // Multiple nested arrays + DATA_2(createTestDataType2(), List.of(createTestData21(), createTestData22())), + + // Array with wildcard type + DATA_3(createTestData31().determineDataType(), List.of(createTestData31(), createTestData32())), + + // Simple values + DATA_4(ValueType.of(), List.of(createTestData41(), createTestData42())), + + // Array with wildcard type + DATA_5(createTestDataType5(), List.of(createTestData51(), createTestData52(), createTestData53())), + + // Tuple with wildcard type + DATA_6(createTestDataType6(), List.of(createTestData61(), createTestData62())); + + + public DataType type; + public List nodes; + + } + + private static DataStructureNode createTestData11() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + var flatArray = ArrayNode.of(List.of(ValueNode.of(1), ValueNode.of(2))); var flatTuple = TupleNode.builder().add("key1", val).build(); - var nestedArray = ArrayNode.wrap(List.of(flatArray, flatTuple)); + var nestedArray = ArrayNode.of(List.of(flatArray, flatTuple)); return TupleNode.builder() .add("key1", val) .add("key2", flatArray) @@ -22,4 +52,109 @@ public class DataStructureTests { .add("key4", nestedArray) .build(); } + + private static DataStructureNode createTestData12() { + var val = ValueNode.nullValue(); + var flatArray = ArrayNode.of(); + var flatTuple = TupleNode.builder().add("key1", val).build(); + var nestedArray = ArrayNode.of(List.of(flatArray, flatTuple)); + return TupleNode.builder() + .add("key1", val) + .add("key2", flatArray) + .add("key3", flatTuple) + .add("key4", nestedArray) + .build(); + } + + private static DataType createTestDataType1() { + return createTestData11().determineDataType(); + } + + public static DataStructureNode createTestData21() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + var flatArray = ArrayNode.of(List.of(ValueNode.of(1), ValueNode.of(2))); + var flatTuple = TupleNode.builder().add("key1", val).build(); + var nestedArray = ArrayNode.of(List.of(flatArray, flatTuple)); + var doubleNested = ArrayNode.of(val, flatArray, flatTuple, nestedArray); + return doubleNested; + } + + public static DataStructureNode createTestData22() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + return ArrayNode.of(val); + } + + public static DataType createTestDataType2() { + return ArrayType.ofWildcard(); + } + + public static DataStructureNode createTestData31() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + var flatTuple = TupleNode.builder().add("key1", val).build(); + var flatArray = ArrayNode.of(List.of(val, flatTuple)); + return flatArray; + } + + public static DataStructureNode createTestData32() { + var val = ValueNode.of("value2".getBytes(StandardCharsets.UTF_8)); + var flatTuple = TupleNode.builder().add("key1", ValueNode.nullValue()).add("key2", ValueNode.nullValue()).build(); + var flatArray = ArrayNode.of(List.of(val, flatTuple)); + return flatArray; + } + + public static DataStructureNode createTestData41() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + return val; + } + + public static DataStructureNode createTestData42() { + var val = ValueNode.nullValue(); + return val; + } + + public static DataStructureNode createTestData51() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + var flatArray = ArrayNode.of(List.of(val, ValueNode.nullValue())); + var array1 = ArrayNode.of(List.of(flatArray)); + var array2 = ArrayNode.of(List.of(array1, array1)); + return array2; + } + + public static DataStructureNode createTestData52() { + var val = ValueNode.of("value2".getBytes(StandardCharsets.UTF_8)); + var flatArray = ArrayNode.of(List.of(val)); + return flatArray; + } + + public static DataStructureNode createTestData53() { + var val = ValueNode.of("value2".getBytes(StandardCharsets.UTF_8)); + var flatTuple = TupleNode.builder().add("key1", val).build(); + var flatArray = ArrayNode.of(List.of(flatTuple, val)); + return flatArray; + } + + public static DataType createTestDataType5() { + return ArrayType.ofWildcard(); + } + + public static DataStructureNode createTestData61() { + var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); + var array = ArrayNode.of(List.of(val, ValueNode.nullValue())); + var tuple = TupleNode.builder() + .add("key1", val).add("key2", array).build(); + return tuple; + } + + public static DataStructureNode createTestData62() { + var val = ValueNode.of("value2".getBytes(StandardCharsets.UTF_8)); + var flatTuple = TupleNode.builder().add("key1", val).build(); + + var tuple = TupleNode.builder() + .add("key1", flatTuple).add("key2", val).build(); + return tuple; + } + + public static DataType createTestDataType6() { + return TupleType.of(List.of("key1", "key2"), List.of(WildcardType.of(), WildcardType.of())); + } } diff --git a/core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java b/core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java deleted file mode 100644 index e7b43ef83..000000000 --- a/core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.xpipe.core.test; - -import io.xpipe.core.data.generic.GenericDataStreamParser; -import io.xpipe.core.data.generic.GenericDataStreamWriter; -import io.xpipe.core.data.generic.GenericDataStructureNodeReader; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.HexFormat; - -import static io.xpipe.core.test.DataStructureTests.createTestData; - -public class GenericDataStructureIoTest { - - @Test - public void testBasicIo() throws IOException { - var obj = createTestData(); - var dataOut = new ByteArrayOutputStream(); - GenericDataStreamWriter.write(dataOut, obj); - var data = dataOut.toByteArray(); - - var format = HexFormat.of().withPrefix("0x").withDelimiter(" "); - System.out.println(format.formatHex(data)); - - var reader = new GenericDataStructureNodeReader(); - GenericDataStreamParser.read(new ByteArrayInputStream(data), reader); - var node = reader.create(); - - Assertions.assertEquals(obj, node); - System.out.println(node); - } -} diff --git a/core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java b/core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java deleted file mode 100644 index d6b7b207d..000000000 --- a/core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.xpipe.core.test; - -import io.xpipe.core.data.typed.TypedDataStreamParser; -import io.xpipe.core.data.typed.TypedDataStreamWriter; -import io.xpipe.core.data.typed.TypedDataStructureNodeReader; -import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.HexFormat; - -import static io.xpipe.core.test.DataStructureTests.createTestData; - -public class TypedDataStructureIoTest { - - @Test - public void testBasicIo() throws IOException { - var obj = createTestData(); - var type = obj.getDataType(); - var dataOut = new ByteArrayOutputStream(); - TypedDataStreamWriter.writeStructure(dataOut, obj, type); - var data = dataOut.toByteArray(); - - var format = HexFormat.of().withPrefix("0x").withDelimiter(" "); - System.out.println(format.formatHex(data)); - - var reader = new TypedDataStructureNodeReader(type); - new TypedDataStreamParser(type).readStructure(new ByteArrayInputStream(data), reader); - var node = reader.create(); - - Assertions.assertEquals(obj, node); - System.out.println(node); - } - - @Test - public void testBasicReusableIo() throws IOException { - var obj = createTestData(); - var type = obj.getDataType(); - var dataOut = new ByteArrayOutputStream(); - TypedDataStreamWriter.writeStructure(dataOut, obj, type); - TypedDataStreamWriter.writeStructure(dataOut, obj, type); - var data = dataOut.toByteArray(); - - var format = HexFormat.of().withPrefix("0x").withDelimiter(" "); - System.out.println(format.formatHex(data)); - - var in = new ByteArrayInputStream(data); - var reader = new TypedReusableDataStructureNodeReader(type); - new TypedDataStreamParser(type).readStructure(in, reader); - var firstNode = reader.create(); - new TypedDataStreamParser(type).readStructure(in, reader); - var secondNode = reader.create(); - - System.out.println(firstNode); - Assertions.assertEquals(obj, firstNode); - Assertions.assertEquals(obj, secondNode); - } -} diff --git a/core/src/test/java/module-info.java b/core/src/test/java/module-info.java index 034b7d4e6..65dcea4a2 100644 --- a/core/src/test/java/module-info.java +++ b/core/src/test/java/module-info.java @@ -4,4 +4,5 @@ module io.xpipe.core.test { requires org.junit.jupiter.api; requires org.junit.jupiter.params; requires io.xpipe.core; + requires static lombok; } \ No newline at end of file