diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java index 1cba03a5b..dcef86073 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java @@ -10,71 +10,75 @@ import java.util.List; public class GenericDataStreamParser { - public static DataStructureNode read(InputStream in) throws IOException { + public static DataStructureNode parse(InputStream in) throws IOException { var reader = new GenericDataStructureNodeReader(); - read(in, reader); + parse(in, reader); return reader.create(); } - public static List readN(InputStream in, int n) throws IOException { + public static List parseN(InputStream in, int n) throws IOException { var list = new ArrayList(); var reader = new GenericDataStructureNodeReader(); for (int i = 0; i < n; i++) { - read(in, reader); + parse(in, reader); list.add(reader.create()); } return list; } - public static void read(InputStream in, GenericDataStreamCallback cb) throws IOException { + public static void parse(InputStream in, GenericDataStreamCallback cb) throws IOException { var b = in.read(); if (b == -1) { return; } + if (b == DataStructureNodeIO.GENERIC_STRUCTURE_ID) { + b = in.read(); + } + switch (b) { case DataStructureNodeIO.GENERIC_TUPLE_ID -> { - readTuple(in, cb); + parseTuple(in, cb); } case DataStructureNodeIO.GENERIC_ARRAY_ID -> { - readArray(in, cb); + parseArray(in, cb); } case DataStructureNodeIO.GENERIC_VALUE_ID -> { - readValue(in, cb); + parseValue(in, cb); } case DataStructureNodeIO.GENERIC_NAME_ID -> { - readName(in, cb); - read(in, cb); + parseName(in, cb); + parse(in, cb); } default -> throw new IllegalStateException("Unexpected type id: " + b); } } - private static void readName(InputStream in, GenericDataStreamCallback cb) throws IOException { + private static void parseName(InputStream in, GenericDataStreamCallback cb) throws IOException { var nameLength = in.read(); var name = new String(in.readNBytes(nameLength)); cb.onName(name); } - private static void readTuple(InputStream in, GenericDataStreamCallback cb) throws IOException { + private static void parseTuple(InputStream in, GenericDataStreamCallback cb) throws IOException { var size = in.read(); cb.onTupleStart(size); for (int i = 0; i < size; i++) { - read(in, cb); + parse(in, cb); } cb.onTupleEnd(); } - private static void readArray(InputStream in, GenericDataStreamCallback cb) throws IOException { + private static void parseArray(InputStream in, GenericDataStreamCallback cb) throws IOException { var size = in.read(); cb.onArrayStart(size); for (int i = 0; i < size; i++) { - read(in, cb); + parse(in, cb); } cb.onArrayEnd(); } - private static void readValue(InputStream in, GenericDataStreamCallback cb) throws IOException { + private static void parseValue(InputStream in, GenericDataStreamCallback cb) throws IOException { var size = in.read(); var data = in.readNBytes(size); cb.onValue(data); diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java index 5f8adfe20..134497997 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java @@ -1,9 +1,6 @@ package io.xpipe.core.data.generic; -import io.xpipe.core.data.node.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.node.*; import java.io.IOException; import java.io.OutputStream; @@ -11,12 +8,12 @@ import java.nio.charset.StandardCharsets; public class GenericDataStreamWriter { - private static final int TUPLE_ID = 1; - private static final int ARRAY_ID = 2; - private static final int VALUE_ID = 3; - private static final int NAME_ID = 4; + public static void writeStructure(OutputStream out, DataStructureNode node) throws IOException { + out.write(DataStructureNodeIO.GENERIC_STRUCTURE_ID); + write(out, node); + } - public static void write(OutputStream out, DataStructureNode node) throws IOException { + private static void write(OutputStream out, DataStructureNode node) throws IOException { if (node.isTuple()) { writeTuple(out, (TupleNode) node); } else if (node.isArray()) { @@ -29,23 +26,25 @@ public class GenericDataStreamWriter { } private static void writeName(OutputStream out, String s) throws IOException { - var b = s.getBytes(StandardCharsets.UTF_8); - out.write(NAME_ID); - out.write(b.length); - out.write(b); + if (s != null) { + var b = s.getBytes(StandardCharsets.UTF_8); + out.write(DataStructureNodeIO.GENERIC_NAME_ID); + out.write(b.length); + out.write(b); + } } private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException { - out.write(TUPLE_ID); + out.write(DataStructureNodeIO.GENERIC_TUPLE_ID); out.write(tuple.size()); for (int i = 0; i < tuple.size(); i++) { - writeName(out, tuple.nameAt(i)); + writeName(out, tuple.keyNameAt(i)); write(out, tuple.at(i)); } } private static void writeArray(OutputStream out, ArrayNode array) throws IOException { - out.write(ARRAY_ID); + out.write(DataStructureNodeIO.GENERIC_ARRAY_ID); out.write(array.size()); for (int i = 0; i < array.size(); i++) { write(out, array.at(i)); @@ -53,7 +52,7 @@ public class GenericDataStreamWriter { } private static void writeValue(OutputStream out, ValueNode value) throws IOException { - out.write(VALUE_ID); + out.write(DataStructureNodeIO.GENERIC_VALUE_ID); out.write(value.getRawData().length); out.write(value.getRawData()); } 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 933159edc..1ce0df6e7 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 @@ -42,6 +42,11 @@ public class GenericTupleReader implements GenericAbstractReader { } private void putNode(DataStructureNode node) { + // If no key was read, assume null key + if (this.names.size() == this.nodes.size()) { + this.names.add(null); + } + this.nodes.add(node); currentIndex++; } 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 387ad1b17..748647fa6 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 @@ -1,100 +1,66 @@ package io.xpipe.core.data.node; import io.xpipe.core.data.type.ArrayType; -import lombok.EqualsAndHashCode; -import java.util.*; -import java.util.function.Consumer; +import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; -import java.util.stream.Stream; -@EqualsAndHashCode(callSuper = false) -public class ArrayNode extends DataStructureNode { - - private final List valueNodes; - - private ArrayNode(List valueNodes) { - this.valueNodes = valueNodes; - } +public abstract class ArrayNode extends DataStructureNode { public static ArrayNode of(DataStructureNode... dsn) { return of(List.of(dsn)); } - public static ArrayNode of(List valueNodes) { - return new ArrayNode(valueNodes); + public static ArrayNode of(List nodes) { + return new SimpleArrayNode(true, nodes); } - public static ArrayNode copyOf(List valueNodes) { - return new ArrayNode(new ArrayList<>(valueNodes)); + public static ArrayNode of(boolean mutable, List nodes) { + return new SimpleArrayNode(mutable, nodes); + } + + protected ArrayNode() { } @Override - public DataStructureNode put(DataStructureNode node) { - valueNodes.add(node); - return this; + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ArrayNode that)) return false; + return getNodes().equals(that.getNodes()); } @Override - public DataStructureNode set(int index, DataStructureNode node) { - valueNodes.add(index, node); - return this; + public int hashCode() { + return Objects.hash(getNodes()); } @Override - public Stream stream() { - return Collections.unmodifiableList(valueNodes).stream(); - } - - @Override - public boolean isArray() { + public final boolean isArray() { return true; } @Override - public int size() { - return valueNodes.size(); - } - - @Override - protected String getName() { + protected final String getName() { return "array node"; } @Override - public String toString(int indent) { - var content = valueNodes.stream().map(n -> n.toString(indent)).collect(Collectors.joining(", ")); - return "[" + content + "]"; + public abstract ArrayNode immutableView(); + + @Override + public abstract ArrayNode mutableCopy(); + + protected abstract String getIdentifier(); + + @Override + public final String toString(int indent) { + var content = getNodes().stream().map(n -> n.toString(indent)).collect(Collectors.joining(", ")); + return "(" + getIdentifier() + ") [" + content + "]"; } @Override - public ArrayType determineDataType() { - return ArrayType.ofSharedType(valueNodes.stream().map(DataStructureNode::determineDataType).toList()); - } - - @Override - public DataStructureNode clear() { - valueNodes.clear(); - return this; - } - - @Override - public DataStructureNode at(int index) { - return valueNodes.get(index); - } - - @Override - public void forEach(Consumer action) { - valueNodes.forEach(action); - } - - @Override - public Spliterator spliterator() { - return valueNodes.spliterator(); - } - - @Override - public Iterator iterator() { - return valueNodes.iterator(); + public final ArrayType determineDataType() { + return ArrayType.ofSharedType(getNodes().stream().map(DataStructureNode::determineDataType).toList()); } } diff --git a/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java b/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java index 6a864eb9e..7a1ab59e3 100644 --- a/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java @@ -3,6 +3,7 @@ package io.xpipe.core.data.node; import io.xpipe.core.data.type.DataType; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.Spliterator; import java.util.function.Consumer; @@ -10,12 +11,37 @@ import java.util.stream.Stream; public abstract class DataStructureNode implements Iterable { + public abstract DataStructureNode mutableCopy(); + + public String keyNameAt(int index) { + throw unsupported("key name at"); + } + + public List getKeyValuePairs() { + throw unsupported("get key value pairs"); + } + + public List getKeyNames() { + throw unsupported("get key names"); + } + + public List getNodes() { + throw unsupported("get nodes"); + } + + public record KeyValue(String key, DataStructureNode value) { + } + protected abstract String getName(); protected UnsupportedOperationException unsupported(String s) { return new UnsupportedOperationException(getName() + " does not support " + s); } + public abstract boolean isMutable(); + + public abstract DataStructureNode immutableView(); + @Override public String toString() { return toString(0); 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 index 78106da69..cca60e36a 100644 --- a/core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java @@ -10,7 +10,22 @@ public class ImmutableValueNode extends ValueNode { @Override public String toString(int indent) { - return getClass().getSimpleName() + "(" + new String(data) + ")"; + return new String(data) + "(I)"; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public ValueNode immutableView() { + return this; + } + + @Override + public ValueNode mutableCopy() { + return ValueNode.mutable(data); } @Override 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 index f4ccefb11..a100359f1 100644 --- a/core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java @@ -10,7 +10,22 @@ public class MutableValueNode extends ValueNode { @Override public String toString(int indent) { - return getClass().getSimpleName() + "(" + new String(data) + ")"; + return new String(data) + "(M)"; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public ValueNode immutableView() { + return new ImmutableValueNode(data); + } + + @Override + public ValueNode mutableCopy() { + return new MutableValueNode(data); } @Override 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 7f2ef757a..9a4e3a478 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 @@ -3,27 +3,46 @@ package io.xpipe.core.data.node; import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.TupleType; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; +import java.util.stream.Collectors; public class NoKeyTupleNode extends TupleNode { + private final boolean mutable; private final List nodes; - NoKeyTupleNode(List nodes) { - this.nodes = nodes; + NoKeyTupleNode(boolean mutable, List nodes) { + this.mutable = mutable; + this.nodes = mutable ? nodes : Collections.unmodifiableList(nodes); + } + + @Override + public TupleNode mutableCopy() { + return new NoKeyTupleNode(true, nodes.stream() + .map(DataStructureNode::mutableCopy) + .collect(Collectors.toCollection(ArrayList::new))); + } + + @Override + public TupleNode immutableView() { + return new NoKeyTupleNode(false, nodes.stream() + .map(DataStructureNode::immutableView) + .collect(Collectors.toCollection(ArrayList::new))); } @Override public DataStructureNode set(int index, DataStructureNode node) { + checkMutable(); + nodes.set(index, node); return this; } @Override public DataType determineDataType() { - return TupleType.of(null, nodes.stream().map(DataStructureNode::determineDataType).toList()); + return TupleType.of(nodes.stream().map(DataStructureNode::determineDataType).toList()); } @Override @@ -31,40 +50,37 @@ public class NoKeyTupleNode extends TupleNode { return "no key tuple node"; } + @Override + public boolean isMutable() { + return mutable; + } + @Override public DataStructureNode at(int index) { return nodes.get(index); } - @Override - public DataStructureNode forKey(String name) { - throw unsupported("key indexing"); - } - - @Override - public Optional forKeyIfPresent(String name) { - return Optional.empty(); - } - @Override public int size() { return nodes.size(); } - public String nameAt(int index) { - throw unsupported("name getter"); - } - @Override public List getKeyValuePairs() { return nodes.stream().map(n -> new KeyValue(null, n)).toList(); } - public List getNames() { + @Override + public List getKeyNames() { return Collections.nCopies(size(), null); } public List getNodes() { - return Collections.unmodifiableList(nodes); + return nodes; + } + + @Override + protected String getIdentifier() { + return "NK"; } } diff --git a/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java new file mode 100644 index 000000000..950af0388 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java @@ -0,0 +1,114 @@ +package io.xpipe.core.data.node; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SimpleArrayNode extends ArrayNode { + + private final boolean mutable; + private final List nodes; + + SimpleArrayNode(boolean mutable, List nodes) { + this.nodes = nodes; + this.mutable = mutable; + } + + private void checkMutable() { + if (!mutable) { + throw new UnsupportedOperationException("Array node is immutable"); + } + } + + @Override + public DataStructureNode put(DataStructureNode node) { + checkMutable(); + + nodes.add(node); + return this; + } + + @Override + public DataStructureNode set(int index, DataStructureNode node) { + checkMutable(); + + nodes.add(index, node); + return this; + } + + @Override + public Stream stream() { + return nodes.stream(); + } + + @Override + public int size() { + return nodes.size(); + } + + @Override + public ArrayNode mutableCopy() { + return new SimpleArrayNode(true, nodes.stream() + .map(DataStructureNode::mutableCopy) + .collect(Collectors.toCollection(ArrayList::new))); + } + + @Override + protected String getIdentifier() { + return "S"; + } + + @Override + public boolean isMutable() { + return mutable; + } + + @Override + public ArrayNode immutableView() { + return new SimpleArrayNode(false, nodes.stream() + .map(DataStructureNode::immutableView) + .collect(Collectors.toCollection(ArrayList::new))); + } + + @Override + public DataStructureNode clear() { + checkMutable(); + + nodes.clear(); + return this; + } + + @Override + public DataStructureNode at(int index) { + return nodes.get(index); + } + + @Override + public void forEach(Consumer action) { + nodes.forEach(action); + } + + @Override + public Spliterator spliterator() { + return nodes.spliterator(); + } + + @Override + public Iterator iterator() { + return nodes.iterator(); + } + + @Override + public List getNodes() { + return nodes; + } + + @Override + public DataStructureNode remove(int index) { + checkMutable(); + + nodes.remove(index); + return this; + } +} 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 ee0fdf281..d2b80091c 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 @@ -2,26 +2,42 @@ package io.xpipe.core.data.node; import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.TupleType; -import lombok.EqualsAndHashCode; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; -@EqualsAndHashCode(callSuper = false) public class SimpleTupleNode extends TupleNode { + private final boolean mutable; private final List names; private final List nodes; - SimpleTupleNode(List names, List nodes) { - this.names = names; - this.nodes = nodes; + SimpleTupleNode(boolean mutable, List names, List nodes) { + this.mutable = mutable; + this.names = mutable ? names : Collections.unmodifiableList(names); + this.nodes = mutable ? nodes : Collections.unmodifiableList(nodes); + } + + @Override + public TupleNode mutableCopy() { + var nodesCopy = nodes.stream() + .map(DataStructureNode::mutableCopy) + .collect(Collectors.toCollection(ArrayList::new)); + return new SimpleTupleNode(true, new ArrayList<>(names), nodesCopy); + } + + @Override + public TupleNode immutableView() { + var nodesCopy = nodes.stream() + .map(DataStructureNode::immutableView) + .collect(Collectors.toCollection(ArrayList::new)); + return new SimpleTupleNode(false, names, nodesCopy); } @Override public DataStructureNode set(int index, DataStructureNode node) { + checkMutable(); + nodes.set(index, node); return this; } @@ -36,6 +52,11 @@ public class SimpleTupleNode extends TupleNode { return "tuple node"; } + @Override + public boolean isMutable() { + return mutable; + } + @Override public DataStructureNode at(int index) { return nodes.get(index); @@ -57,6 +78,8 @@ public class SimpleTupleNode extends TupleNode { @Override public DataStructureNode clear() { + checkMutable(); + nodes.clear(); names.clear(); return this; @@ -68,7 +91,7 @@ public class SimpleTupleNode extends TupleNode { return nodes.size(); } - public String nameAt(int index) { + public String keyNameAt(int index) { return names.get(index); } @@ -76,16 +99,21 @@ public class SimpleTupleNode extends TupleNode { public List getKeyValuePairs() { var l = new ArrayList(size()); for (int i = 0; i < size(); i++) { - l.add(new KeyValue(getNames().get(i), getNodes().get(i))); + l.add(new KeyValue(getKeyNames().get(i), getNodes().get(i))); } return l; } - public List getNames() { + public List getKeyNames() { return Collections.unmodifiableList(names); } public List getNodes() { return Collections.unmodifiableList(nodes); } + + @Override + protected String getIdentifier() { + return "S"; + } } diff --git a/core/src/main/java/io/xpipe/core/data/node/TupleNode.java b/core/src/main/java/io/xpipe/core/data/node/TupleNode.java index 69b9515e7..14c44fdd9 100644 --- a/core/src/main/java/io/xpipe/core/data/node/TupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/TupleNode.java @@ -1,7 +1,5 @@ package io.xpipe.core.data.node; -import lombok.Value; - import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -18,11 +16,7 @@ public abstract class TupleNode extends DataStructureNode { throw new IllegalArgumentException("Nodes must be not null"); } - return new NoKeyTupleNode(nodes); - } - - public static TupleNode copyOf(List nodes) { - return TupleNode.of(List.copyOf(nodes)); + return new NoKeyTupleNode(true, nodes); } public static TupleNode of(List names, List nodes) { @@ -36,55 +30,62 @@ public abstract class TupleNode extends DataStructureNode { throw new IllegalArgumentException("Names and nodes must have the same length"); } - return new SimpleTupleNode(names, nodes); + return new SimpleTupleNode(true, names, nodes); } - public static TupleNode ofRaw(List names, List nodes) { + public static TupleNode of(boolean mutable, List names, List nodes) { if (names == null) { throw new IllegalArgumentException("Names must be not null"); } if (nodes == null) { throw new IllegalArgumentException("Nodes must be not null"); } - return new SimpleTupleNode(names, nodes); - } - - public static TupleNode copyOf(List names, List nodes) { - return TupleNode.of(List.copyOf(names), List.copyOf(nodes)); + return new SimpleTupleNode(mutable, names, nodes); } public final boolean isTuple() { return true; } + protected abstract String getIdentifier(); + + protected void checkMutable() { + if (!isMutable()) { + throw new UnsupportedOperationException("Tuple node is immutable"); + } + } + + @Override + public abstract TupleNode mutableCopy(); + + @Override + public abstract TupleNode immutableView(); + @Override public String toString(int indent) { var is = " ".repeat(indent); - var start = getClass().getSimpleName() + " {\n"; + var start = "(" + getIdentifier() + ") {\n"; var kvs = getKeyValuePairs().stream().map(kv -> { - if (kv.key == null) { - return is + " " + kv.value.toString(indent + 1) + "\n"; + if (kv.key() == null) { + return is + " " + kv.value().toString(indent + 1) + "\n"; } else { - return is + " " + kv.key + "=" + kv.value.toString(indent + 1) + "\n"; + return is + " " + kv.key() + "=" + kv.value().toString(indent + 1) + "\n"; } }).collect(Collectors.joining()); var end = is + "}"; return start + kvs + end; } - public abstract String nameAt(int index); + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TupleNode that)) return false; + return getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes()); + } - public abstract List getKeyValuePairs(); - - public abstract List getNames(); - - public abstract List getNodes(); - - @Value - public static class KeyValue { - - String key; - DataStructureNode value; + @Override + public int hashCode() { + return Objects.hash(getKeyNames(), getNodes()); } public static class Builder { @@ -104,11 +105,11 @@ public abstract class TupleNode extends DataStructureNode { } public TupleNode build() { - boolean hasKeys = entries.stream().anyMatch(kv -> kv.key != null); + boolean hasKeys = entries.stream().anyMatch(kv -> kv.key() != null); return hasKeys ? TupleNode.of( - entries.stream().map(kv -> kv.key).toList(), - entries.stream().map(kv -> kv.value).toList()) : - TupleNode.of(entries.stream().map(kv -> kv.value).toList()); + entries.stream().map(KeyValue::key).toList(), + entries.stream().map(KeyValue::value).toList()) : + TupleNode.of(entries.stream().map(KeyValue::value).toList()); } } } 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 f46a0e4e8..ae3cdc67d 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 @@ -2,11 +2,10 @@ package io.xpipe.core.data.node; import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.ValueType; -import lombok.EqualsAndHashCode; import java.nio.charset.StandardCharsets; +import java.util.Arrays; -@EqualsAndHashCode(callSuper = false) public abstract class ValueNode extends DataStructureNode { private static final byte[] NULL = new byte[]{0}; @@ -14,6 +13,24 @@ public abstract class ValueNode extends DataStructureNode { protected ValueNode() { } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ValueNode that)) return false; + return Arrays.equals(getRawData(), that.getRawData()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getRawData()); + } + + @Override + public abstract ValueNode immutableView(); + + @Override + public abstract ValueNode mutableCopy(); + public static ValueNode immutable(byte[] data) { return new ImmutableValueNode(data); } diff --git a/core/src/main/java/io/xpipe/core/data/type/DataTypeVisitors.java b/core/src/main/java/io/xpipe/core/data/type/DataTypeVisitors.java index 3fd105036..9525fac8a 100644 --- a/core/src/main/java/io/xpipe/core/data/type/DataTypeVisitors.java +++ b/core/src/main/java/io/xpipe/core/data/type/DataTypeVisitors.java @@ -27,6 +27,7 @@ public class DataTypeVisitors { @Override public void onArray(ArrayType type) { typeConsumer.accept(type); + typeConsumer.accept(type.getSharedType()); } @Override 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 f5f463a36..a7192c6d1 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 @@ -68,11 +68,11 @@ public class TupleType extends DataType { int counter = 0; for (var kv : t.getKeyValuePairs()) { - if (!Objects.equals(kv.getKey(), names.get(counter))) { + if (!Objects.equals(kv.key(), names.get(counter))) { return false; } - if (!types.get(counter).matches(kv.getValue())) { + if (!types.get(counter).matches(kv.value())) { return false; } counter++; diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java index d8ac716c0..f1e9b68db 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java @@ -3,8 +3,6 @@ package io.xpipe.core.data.typed; import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.type.TupleType; -import java.io.IOException; - public interface TypedDataStreamCallback { default void onValue(byte[] data) { @@ -19,7 +17,7 @@ public interface TypedDataStreamCallback { default void onTupleEnd() { } - default void onArrayBegin(int size) throws IOException { + default void onArrayBegin(int size) { } default void onArrayEnd() { diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java index e84c0f5e0..54c07b636 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java @@ -90,9 +90,16 @@ public class TypedDataStreamParser { parseValue(in, cb); } + case DataStructureNodeIO.GENERIC_STRUCTURE_ID -> { + if (!type.isWildcard()) { + throw new IllegalStateException("Got structure but expected " + type.getName()); + } + + GenericDataStreamParser.parse(in, getGenericReader()); + cb.onGenericNode(getGenericReader().create()); + } default -> { - GenericDataStreamParser. - throw new IllegalStateException("Unexpected value: " + b); + throw new IllegalStateException("Unexpected type id: " + b); } } } @@ -100,14 +107,7 @@ public class TypedDataStreamParser { private void parseTypedTuple(InputStream in, TypedDataStreamCallback cb, TupleType type) throws IOException { cb.onTupleBegin(type); for (int i = 0; i < type.getSize(); i++) { - if (type.getTypes().get(i).isWildcard()) { - var r = getGenericReader(); - GenericDataStreamParser.read(in, r); - var node = r.create(); - cb.onGenericNode(node); - } else { - parse(in, cb, type.getTypes().get(i)); - } + parse(in, cb, type.getTypes().get(i)); } cb.onTupleEnd(); } @@ -123,14 +123,7 @@ public class TypedDataStreamParser { var size = in.read(); cb.onArrayBegin(size); for (int i = 0; i < size; i++) { - if (type.getSharedType().isWildcard()) { - var r = getGenericReader(); - GenericDataStreamParser.read(in, r); - var node = r.create(); - cb.onGenericNode(node); - } else { - parse(in, cb, type.getSharedType()); - } + parse(in, cb, type.getSharedType()); } cb.onArrayEnd(); } 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 1bbd126ed..0a1433758 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 @@ -28,7 +28,7 @@ public class TypedDataStreamWriter { } else if (node.isValue() && type.isValue()) { writeValue(out, (ValueNode) node); } else if (type.isWildcard()) { - GenericDataStreamWriter.write(out, node); + GenericDataStreamWriter.writeStructure(out, node); } else { throw new IllegalStateException("Incompatible node and type"); } 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 a914c8990..2c8abd4d3 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 @@ -1,15 +1,10 @@ package io.xpipe.core.data.typed; -import io.xpipe.core.data.node.DataStructureNode; -import io.xpipe.core.data.node.ArrayNode; -import io.xpipe.core.data.node.SimpleTupleNode; -import io.xpipe.core.data.node.TupleNode; -import io.xpipe.core.data.node.ValueNode; +import io.xpipe.core.data.node.*; import io.xpipe.core.data.type.DataType; -import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.DataTypeVisitors; +import io.xpipe.core.data.type.TupleType; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -17,22 +12,6 @@ import java.util.Stack; public class TypedDataStructureNodeReader implements TypedAbstractReader { - private final List flattened; - private final Stack> children; - private final Stack nodes; - private final boolean makeImmutable; - private int currentDataTypeIndex; - private DataStructureNode readNode; - private boolean initialized; - private int arrayDepth; - private TypedDataStructureNodeReader(DataType type, boolean makeImmutable) { - flattened = new ArrayList<>(); - children = new Stack<>(); - nodes = new Stack<>(); - type.visit(DataTypeVisitors.flatten(d -> flattened.add(d))); - this.makeImmutable = makeImmutable; - } - public static TypedDataStructureNodeReader mutable(DataType type) { return new TypedDataStructureNodeReader(type, false); } @@ -41,10 +20,30 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { return new TypedDataStructureNodeReader(type, true); } + private final boolean makeImmutable; + private DataStructureNode readNode; + + private final Stack> children; + private final Stack nodes; + private int arrayDepth; + + private final List flattened; + private DataType expectedType; + private int currentExpectedTypeIndex; + + private TypedDataStructureNodeReader(DataType type, boolean makeImmutable) { + flattened = new ArrayList<>(); + type.visit(DataTypeVisitors.flatten(flattened::add)); + children = new Stack<>(); + nodes = new Stack<>(); + this.makeImmutable = makeImmutable; + expectedType = flattened.get(0); + } + @Override public void onNodeBegin() { if (nodes.size() != 0 || children.size() != 0) { - throw new IllegalStateException(); + throw new IllegalStateException("Reader did not completely reset"); } readNode = null; @@ -56,30 +55,37 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { } public DataStructureNode create() { + if (readNode == null) { + throw new IllegalStateException("Reader is not finished yet"); + } + return readNode; } @Override public void onNodeEnd() { if (nodes.size() != 0 || children.size() != 0 || readNode == null) { - throw new IllegalStateException(); + throw new IllegalStateException("Reader is not finished yet"); } + } - initialized = false; + private void finishNode(DataStructureNode node) { + if (nodes.empty()) { + readNode = node; + } else { + children.peek().add(node); + } } @Override public void onValue(byte[] data) { - var val = makeImmutable ? ValueNode.immutable(data) : ValueNode.mutable(data); - if (!initialized) { - readNode = val; - return; + if (!expectedType.isValue()) { + throw new IllegalStateException("Expected " + expectedType.getName() + " but got value"); } - children.peek().add(val); - if (!flattened.get(currentDataTypeIndex).isArray()) { - currentDataTypeIndex++; - } + var val = makeImmutable ? ValueNode.immutable(data) : ValueNode.mutable(data); + finishNode(val); + moveExpectedType(false); } private boolean isInArray() { @@ -88,25 +94,22 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { @Override public void onGenericNode(DataStructureNode node) { - children.peek().add(node); - if (!isInArray()) { - currentDataTypeIndex++; + if (!expectedType.isWildcard()) { + throw new IllegalStateException("Expected " + expectedType.getName() + " but got generic node"); } + + finishNode(makeImmutable ? node.immutableView() : node); + moveExpectedType(false); } @Override public void onTupleBegin(TupleType type) { - if (!isInArray() && !flattened.get(currentDataTypeIndex).isTuple()) { - throw new IllegalStateException(); + if (!expectedType.isTuple()) { + throw new IllegalStateException("Expected " + expectedType.getName() + " but got tuple"); } - TupleType tupleType = (TupleType) flattened.get(currentDataTypeIndex); - if (!initialized || !flattened.get(currentDataTypeIndex).isArray()) { - currentDataTypeIndex++; - } - if (!initialized) { - initialized = true; - } + TupleType tupleType = expectedType.asTuple(); + moveExpectedType(false); var l = new ArrayList(tupleType.getSize()); children.push(l); @@ -114,7 +117,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { var tupleNames = makeImmutable ? Collections.unmodifiableList(tupleType.getNames()) : new ArrayList<>(tupleType.getNames()); var tupleNodes = makeImmutable ? Collections.unmodifiableList(l) : l; - var newNode = TupleNode.ofRaw(tupleNames, tupleNodes); + var newNode = TupleNode.of(!makeImmutable, tupleNames, tupleNodes); nodes.push(newNode); } @@ -123,49 +126,52 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { children.pop(); var popped = nodes.pop(); if (!popped.isTuple()) { - throw new IllegalStateException(); + throw new IllegalStateException("No tuple to end"); } - SimpleTupleNode tuple = (SimpleTupleNode) popped; - if (tuple.getNames().size() != tuple.getNodes().size()) { - throw new IllegalStateException(""); + TupleNode tuple = popped.asTuple(); + if (tuple.getKeyNames().size() != tuple.getNodes().size()) { + throw new IllegalStateException("Tuple node size mismatch"); } - if (nodes.empty()) { - readNode = popped; - } else { - children.peek().add(popped); + finishNode(popped); + } + + private void moveExpectedType(boolean force) { + if (!isInArray() || force) { + currentExpectedTypeIndex++; + expectedType = currentExpectedTypeIndex == flattened.size() ? null : flattened.get(currentExpectedTypeIndex); } } @Override - public void onArrayBegin(int size) throws IOException { - if (!flattened.get(currentDataTypeIndex).isArray()) { - throw new IllegalStateException(); + public void onArrayBegin(int size) { + if (!expectedType.isArray()) { + throw new IllegalStateException("Expected " + expectedType.getName() + " but got array"); } + arrayDepth++; + moveExpectedType(true); + var l = new ArrayList(); children.push(l); var arrayNodes = makeImmutable ? Collections.unmodifiableList(l) : l; var newNode = ArrayNode.of(arrayNodes); nodes.push(newNode); - arrayDepth++; } @Override public void onArrayEnd() { - arrayDepth--; if (!isInArray()) { - currentDataTypeIndex++; + throw new IllegalStateException("No array to end"); } + arrayDepth--; + moveExpectedType(true); + children.pop(); var popped = nodes.pop(); - if (nodes.empty()) { - readNode = popped; - } else { - children.peek().add(popped); - } + finishNode(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 2fbccaf62..065cdad7b 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 @@ -3,10 +3,9 @@ package io.xpipe.core.data.typed; import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.ValueNode; import io.xpipe.core.data.type.DataType; -import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.DataTypeVisitors; +import io.xpipe.core.data.type.TupleType; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Stack; @@ -43,7 +42,6 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader return arrayDepth >= 1; } - private boolean initialized() { return node != null; } @@ -73,13 +71,25 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader return; } - getCurrentParent().set(indices.peek(), node); + if (hasParent()) { + getCurrentParent().set(indices.peek(), node); + } else { + this.node = node; + } if (!indices.isEmpty()) { indices.push(indices.pop() + 1); } } + private boolean hasParent() { + return indices.size() > 0; + } + private DataStructureNode getCurrentParent() { + if (!hasParent()) { + throw new IllegalStateException("No parent available"); + } + var current = node; for (var index : indices.subList(0, indices.size() - 1)) { current = current.at(index); @@ -95,15 +105,6 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader return current; } - private void setValue(byte[] data) { - var current = node; - for (var index : indices) { - current = current.at(index); - } - var value = (ValueNode) current; - value.setRawData(data); - } - @Override public void onTupleBegin(TupleType type) { if (!initialized()) { @@ -128,7 +129,7 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader } @Override - public void onArrayBegin(int size) throws IOException { + public void onArrayBegin(int size) { if (!initialized()) { initialReader.onArrayBegin(size); return; @@ -157,7 +158,6 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader public void onNodeBegin() { if (!initialized()) { initialReader.onNodeBegin(); - return; } } 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 6a2938b5a..07451d760 100644 --- a/core/src/test/java/io/xpipe/core/test/DataStructureTest.java +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTest.java @@ -79,10 +79,10 @@ public class DataStructureTest { public void testGenericIo(DataStructureTests.TypedDataset ds) throws IOException { for (var el : ds.nodes) { var dataOut = new ByteArrayOutputStream(); - GenericDataStreamWriter.write(dataOut, el); + GenericDataStreamWriter.writeStructure(dataOut, el); var data = dataOut.toByteArray(); var reader = new GenericDataStructureNodeReader(); - GenericDataStreamParser.read(new ByteArrayInputStream(data), reader); + GenericDataStreamParser.parse(new ByteArrayInputStream(data), reader); var readNode = reader.create(); Assertions.assertEquals(el, 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 751f3b8b5..a74c5a792 100644 --- a/core/src/test/java/io/xpipe/core/test/DataStructureTests.java +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTests.java @@ -8,6 +8,7 @@ import io.xpipe.core.data.type.*; import lombok.AllArgsConstructor; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; public class DataStructureTests { @@ -144,7 +145,7 @@ public class DataStructureTests { 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(); + .add(val).add("key2", array).build(); return tuple; } @@ -153,12 +154,15 @@ public class DataStructureTests { var flatTuple = TupleNode.builder().add("key1", val).build(); var tuple = TupleNode.builder() - .add("key1", flatTuple).add("key2", val).build(); + .add(flatTuple).add("key2", val).build(); return tuple; } public static DataType createTestDataType6() { - return TupleType.of(List.of("key1", "key2"), List.of(WildcardType.of(), WildcardType.of())); + var keys = new ArrayList(); + keys.add(null); + keys.add("key2"); + return TupleType.of(keys, List.of(WildcardType.of(), WildcardType.of())); } public static DataStructureNode createTestData71() {