Rework mutability

This commit is contained in:
Christopher Schnick 2021-12-19 15:34:58 +01:00
parent df9495e0a3
commit b54d8ad362
21 changed files with 486 additions and 278 deletions

View file

@ -10,71 +10,75 @@ import java.util.List;
public class GenericDataStreamParser { public class GenericDataStreamParser {
public static DataStructureNode read(InputStream in) throws IOException { public static DataStructureNode parse(InputStream in) throws IOException {
var reader = new GenericDataStructureNodeReader(); var reader = new GenericDataStructureNodeReader();
read(in, reader); parse(in, reader);
return reader.create(); return reader.create();
} }
public static List<DataStructureNode> readN(InputStream in, int n) throws IOException { public static List<DataStructureNode> parseN(InputStream in, int n) throws IOException {
var list = new ArrayList<DataStructureNode>(); var list = new ArrayList<DataStructureNode>();
var reader = new GenericDataStructureNodeReader(); var reader = new GenericDataStructureNodeReader();
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
read(in, reader); parse(in, reader);
list.add(reader.create()); list.add(reader.create());
} }
return list; 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(); var b = in.read();
if (b == -1) { if (b == -1) {
return; return;
} }
if (b == DataStructureNodeIO.GENERIC_STRUCTURE_ID) {
b = in.read();
}
switch (b) { switch (b) {
case DataStructureNodeIO.GENERIC_TUPLE_ID -> { case DataStructureNodeIO.GENERIC_TUPLE_ID -> {
readTuple(in, cb); parseTuple(in, cb);
} }
case DataStructureNodeIO.GENERIC_ARRAY_ID -> { case DataStructureNodeIO.GENERIC_ARRAY_ID -> {
readArray(in, cb); parseArray(in, cb);
} }
case DataStructureNodeIO.GENERIC_VALUE_ID -> { case DataStructureNodeIO.GENERIC_VALUE_ID -> {
readValue(in, cb); parseValue(in, cb);
} }
case DataStructureNodeIO.GENERIC_NAME_ID -> { case DataStructureNodeIO.GENERIC_NAME_ID -> {
readName(in, cb); parseName(in, cb);
read(in, cb); parse(in, cb);
} }
default -> throw new IllegalStateException("Unexpected type id: " + b); 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 nameLength = in.read();
var name = new String(in.readNBytes(nameLength)); var name = new String(in.readNBytes(nameLength));
cb.onName(name); 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(); var size = in.read();
cb.onTupleStart(size); cb.onTupleStart(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
read(in, cb); parse(in, cb);
} }
cb.onTupleEnd(); 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(); var size = in.read();
cb.onArrayStart(size); cb.onArrayStart(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
read(in, cb); parse(in, cb);
} }
cb.onArrayEnd(); 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 size = in.read();
var data = in.readNBytes(size); var data = in.readNBytes(size);
cb.onValue(data); cb.onValue(data);

View file

@ -1,9 +1,6 @@
package io.xpipe.core.data.generic; package io.xpipe.core.data.generic;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.*;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.node.ValueNode;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -11,12 +8,12 @@ import java.nio.charset.StandardCharsets;
public class GenericDataStreamWriter { public class GenericDataStreamWriter {
private static final int TUPLE_ID = 1; public static void writeStructure(OutputStream out, DataStructureNode node) throws IOException {
private static final int ARRAY_ID = 2; out.write(DataStructureNodeIO.GENERIC_STRUCTURE_ID);
private static final int VALUE_ID = 3; write(out, node);
private static final int NAME_ID = 4; }
public static void write(OutputStream out, DataStructureNode node) throws IOException { private static void write(OutputStream out, DataStructureNode node) throws IOException {
if (node.isTuple()) { if (node.isTuple()) {
writeTuple(out, (TupleNode) node); writeTuple(out, (TupleNode) node);
} else if (node.isArray()) { } else if (node.isArray()) {
@ -29,23 +26,25 @@ public class GenericDataStreamWriter {
} }
private static void writeName(OutputStream out, String s) throws IOException { private static void writeName(OutputStream out, String s) throws IOException {
var b = s.getBytes(StandardCharsets.UTF_8); if (s != null) {
out.write(NAME_ID); var b = s.getBytes(StandardCharsets.UTF_8);
out.write(b.length); out.write(DataStructureNodeIO.GENERIC_NAME_ID);
out.write(b); out.write(b.length);
out.write(b);
}
} }
private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException { private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException {
out.write(TUPLE_ID); out.write(DataStructureNodeIO.GENERIC_TUPLE_ID);
out.write(tuple.size()); out.write(tuple.size());
for (int i = 0; i < tuple.size(); i++) { for (int i = 0; i < tuple.size(); i++) {
writeName(out, tuple.nameAt(i)); writeName(out, tuple.keyNameAt(i));
write(out, tuple.at(i)); write(out, tuple.at(i));
} }
} }
private static void writeArray(OutputStream out, ArrayNode array) throws IOException { private static void writeArray(OutputStream out, ArrayNode array) throws IOException {
out.write(ARRAY_ID); out.write(DataStructureNodeIO.GENERIC_ARRAY_ID);
out.write(array.size()); out.write(array.size());
for (int i = 0; i < array.size(); i++) { for (int i = 0; i < array.size(); i++) {
write(out, array.at(i)); write(out, array.at(i));
@ -53,7 +52,7 @@ public class GenericDataStreamWriter {
} }
private static void writeValue(OutputStream out, ValueNode value) throws IOException { 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().length);
out.write(value.getRawData()); out.write(value.getRawData());
} }

View file

@ -42,6 +42,11 @@ public class GenericTupleReader implements GenericAbstractReader {
} }
private void putNode(DataStructureNode node) { 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); this.nodes.add(node);
currentIndex++; currentIndex++;
} }

View file

@ -1,100 +1,66 @@
package io.xpipe.core.data.node; package io.xpipe.core.data.node;
import io.xpipe.core.data.type.ArrayType; import io.xpipe.core.data.type.ArrayType;
import lombok.EqualsAndHashCode;
import java.util.*; import java.util.List;
import java.util.function.Consumer; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
@EqualsAndHashCode(callSuper = false) public abstract class ArrayNode extends DataStructureNode {
public class ArrayNode extends DataStructureNode {
private final List<DataStructureNode> valueNodes;
private ArrayNode(List<DataStructureNode> valueNodes) {
this.valueNodes = valueNodes;
}
public static ArrayNode of(DataStructureNode... dsn) { public static ArrayNode of(DataStructureNode... dsn) {
return of(List.of(dsn)); return of(List.of(dsn));
} }
public static ArrayNode of(List<DataStructureNode> valueNodes) { public static ArrayNode of(List<DataStructureNode> nodes) {
return new ArrayNode(valueNodes); return new SimpleArrayNode(true, nodes);
} }
public static ArrayNode copyOf(List<DataStructureNode> valueNodes) { public static ArrayNode of(boolean mutable, List<DataStructureNode> nodes) {
return new ArrayNode(new ArrayList<>(valueNodes)); return new SimpleArrayNode(mutable, nodes);
}
protected ArrayNode() {
} }
@Override @Override
public DataStructureNode put(DataStructureNode node) { public boolean equals(Object o) {
valueNodes.add(node); if (this == o) return true;
return this; if (!(o instanceof ArrayNode that)) return false;
return getNodes().equals(that.getNodes());
} }
@Override @Override
public DataStructureNode set(int index, DataStructureNode node) { public int hashCode() {
valueNodes.add(index, node); return Objects.hash(getNodes());
return this;
} }
@Override @Override
public Stream<DataStructureNode> stream() { public final boolean isArray() {
return Collections.unmodifiableList(valueNodes).stream();
}
@Override
public boolean isArray() {
return true; return true;
} }
@Override @Override
public int size() { protected final String getName() {
return valueNodes.size();
}
@Override
protected String getName() {
return "array node"; return "array node";
} }
@Override @Override
public String toString(int indent) { public abstract ArrayNode immutableView();
var content = valueNodes.stream().map(n -> n.toString(indent)).collect(Collectors.joining(", "));
return "[" + content + "]"; @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 @Override
public ArrayType determineDataType() { public final ArrayType determineDataType() {
return ArrayType.ofSharedType(valueNodes.stream().map(DataStructureNode::determineDataType).toList()); return ArrayType.ofSharedType(getNodes().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<? super DataStructureNode> action) {
valueNodes.forEach(action);
}
@Override
public Spliterator<DataStructureNode> spliterator() {
return valueNodes.spliterator();
}
@Override
public Iterator<DataStructureNode> iterator() {
return valueNodes.iterator();
} }
} }

View file

@ -3,6 +3,7 @@ package io.xpipe.core.data.node;
import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.DataType;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Spliterator; import java.util.Spliterator;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -10,12 +11,37 @@ import java.util.stream.Stream;
public abstract class DataStructureNode implements Iterable<DataStructureNode> { public abstract class DataStructureNode implements Iterable<DataStructureNode> {
public abstract DataStructureNode mutableCopy();
public String keyNameAt(int index) {
throw unsupported("key name at");
}
public List<KeyValue> getKeyValuePairs() {
throw unsupported("get key value pairs");
}
public List<String> getKeyNames() {
throw unsupported("get key names");
}
public List<DataStructureNode> getNodes() {
throw unsupported("get nodes");
}
public record KeyValue(String key, DataStructureNode value) {
}
protected abstract String getName(); protected abstract String getName();
protected UnsupportedOperationException unsupported(String s) { protected UnsupportedOperationException unsupported(String s) {
return new UnsupportedOperationException(getName() + " does not support " + s); return new UnsupportedOperationException(getName() + " does not support " + s);
} }
public abstract boolean isMutable();
public abstract DataStructureNode immutableView();
@Override @Override
public String toString() { public String toString() {
return toString(0); return toString(0);

View file

@ -10,7 +10,22 @@ public class ImmutableValueNode extends ValueNode {
@Override @Override
public String toString(int indent) { 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 @Override

View file

@ -10,7 +10,22 @@ public class MutableValueNode extends ValueNode {
@Override @Override
public String toString(int indent) { 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 @Override

View file

@ -3,27 +3,46 @@ package io.xpipe.core.data.node;
import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.DataType;
import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.TupleType;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.stream.Collectors;
public class NoKeyTupleNode extends TupleNode { public class NoKeyTupleNode extends TupleNode {
private final boolean mutable;
private final List<DataStructureNode> nodes; private final List<DataStructureNode> nodes;
NoKeyTupleNode(List<DataStructureNode> nodes) { NoKeyTupleNode(boolean mutable, List<DataStructureNode> nodes) {
this.nodes = 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 @Override
public DataStructureNode set(int index, DataStructureNode node) { public DataStructureNode set(int index, DataStructureNode node) {
checkMutable();
nodes.set(index, node); nodes.set(index, node);
return this; return this;
} }
@Override @Override
public DataType determineDataType() { public DataType determineDataType() {
return TupleType.of(null, nodes.stream().map(DataStructureNode::determineDataType).toList()); return TupleType.of(nodes.stream().map(DataStructureNode::determineDataType).toList());
} }
@Override @Override
@ -31,40 +50,37 @@ public class NoKeyTupleNode extends TupleNode {
return "no key tuple node"; return "no key tuple node";
} }
@Override
public boolean isMutable() {
return mutable;
}
@Override @Override
public DataStructureNode at(int index) { public DataStructureNode at(int index) {
return nodes.get(index); return nodes.get(index);
} }
@Override
public DataStructureNode forKey(String name) {
throw unsupported("key indexing");
}
@Override
public Optional<DataStructureNode> forKeyIfPresent(String name) {
return Optional.empty();
}
@Override @Override
public int size() { public int size() {
return nodes.size(); return nodes.size();
} }
public String nameAt(int index) {
throw unsupported("name getter");
}
@Override @Override
public List<KeyValue> getKeyValuePairs() { public List<KeyValue> getKeyValuePairs() {
return nodes.stream().map(n -> new KeyValue(null, n)).toList(); return nodes.stream().map(n -> new KeyValue(null, n)).toList();
} }
public List<String> getNames() { @Override
public List<String> getKeyNames() {
return Collections.nCopies(size(), null); return Collections.nCopies(size(), null);
} }
public List<DataStructureNode> getNodes() { public List<DataStructureNode> getNodes() {
return Collections.unmodifiableList(nodes); return nodes;
}
@Override
protected String getIdentifier() {
return "NK";
} }
} }

View file

@ -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<DataStructureNode> nodes;
SimpleArrayNode(boolean mutable, List<DataStructureNode> 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<DataStructureNode> 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<? super DataStructureNode> action) {
nodes.forEach(action);
}
@Override
public Spliterator<DataStructureNode> spliterator() {
return nodes.spliterator();
}
@Override
public Iterator<DataStructureNode> iterator() {
return nodes.iterator();
}
@Override
public List<DataStructureNode> getNodes() {
return nodes;
}
@Override
public DataStructureNode remove(int index) {
checkMutable();
nodes.remove(index);
return this;
}
}

View file

@ -2,26 +2,42 @@ package io.xpipe.core.data.node;
import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.DataType;
import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.TupleType;
import lombok.EqualsAndHashCode;
import java.util.ArrayList; import java.util.*;
import java.util.Collections; import java.util.stream.Collectors;
import java.util.List;
import java.util.Optional;
@EqualsAndHashCode(callSuper = false)
public class SimpleTupleNode extends TupleNode { public class SimpleTupleNode extends TupleNode {
private final boolean mutable;
private final List<String> names; private final List<String> names;
private final List<DataStructureNode> nodes; private final List<DataStructureNode> nodes;
SimpleTupleNode(List<String> names, List<DataStructureNode> nodes) { SimpleTupleNode(boolean mutable, List<String> names, List<DataStructureNode> nodes) {
this.names = names; this.mutable = mutable;
this.nodes = nodes; 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 @Override
public DataStructureNode set(int index, DataStructureNode node) { public DataStructureNode set(int index, DataStructureNode node) {
checkMutable();
nodes.set(index, node); nodes.set(index, node);
return this; return this;
} }
@ -36,6 +52,11 @@ public class SimpleTupleNode extends TupleNode {
return "tuple node"; return "tuple node";
} }
@Override
public boolean isMutable() {
return mutable;
}
@Override @Override
public DataStructureNode at(int index) { public DataStructureNode at(int index) {
return nodes.get(index); return nodes.get(index);
@ -57,6 +78,8 @@ public class SimpleTupleNode extends TupleNode {
@Override @Override
public DataStructureNode clear() { public DataStructureNode clear() {
checkMutable();
nodes.clear(); nodes.clear();
names.clear(); names.clear();
return this; return this;
@ -68,7 +91,7 @@ public class SimpleTupleNode extends TupleNode {
return nodes.size(); return nodes.size();
} }
public String nameAt(int index) { public String keyNameAt(int index) {
return names.get(index); return names.get(index);
} }
@ -76,16 +99,21 @@ public class SimpleTupleNode extends TupleNode {
public List<KeyValue> getKeyValuePairs() { public List<KeyValue> getKeyValuePairs() {
var l = new ArrayList<KeyValue>(size()); var l = new ArrayList<KeyValue>(size());
for (int i = 0; i < size(); i++) { 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; return l;
} }
public List<String> getNames() { public List<String> getKeyNames() {
return Collections.unmodifiableList(names); return Collections.unmodifiableList(names);
} }
public List<DataStructureNode> getNodes() { public List<DataStructureNode> getNodes() {
return Collections.unmodifiableList(nodes); return Collections.unmodifiableList(nodes);
} }
@Override
protected String getIdentifier() {
return "S";
}
} }

View file

@ -1,7 +1,5 @@
package io.xpipe.core.data.node; package io.xpipe.core.data.node;
import lombok.Value;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -18,11 +16,7 @@ public abstract class TupleNode extends DataStructureNode {
throw new IllegalArgumentException("Nodes must be not null"); throw new IllegalArgumentException("Nodes must be not null");
} }
return new NoKeyTupleNode(nodes); return new NoKeyTupleNode(true, nodes);
}
public static TupleNode copyOf(List<DataStructureNode> nodes) {
return TupleNode.of(List.copyOf(nodes));
} }
public static TupleNode of(List<String> names, List<DataStructureNode> nodes) { public static TupleNode of(List<String> names, List<DataStructureNode> nodes) {
@ -36,55 +30,62 @@ public abstract class TupleNode extends DataStructureNode {
throw new IllegalArgumentException("Names and nodes must have the same length"); 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<String> names, List<DataStructureNode> nodes) { public static TupleNode of(boolean mutable, List<String> names, List<DataStructureNode> nodes) {
if (names == null) { if (names == null) {
throw new IllegalArgumentException("Names must be not null"); throw new IllegalArgumentException("Names must be not null");
} }
if (nodes == null) { if (nodes == null) {
throw new IllegalArgumentException("Nodes must be not null"); throw new IllegalArgumentException("Nodes must be not null");
} }
return new SimpleTupleNode(names, nodes); return new SimpleTupleNode(mutable, names, nodes);
}
public static TupleNode copyOf(List<String> names, List<DataStructureNode> nodes) {
return TupleNode.of(List.copyOf(names), List.copyOf(nodes));
} }
public final boolean isTuple() { public final boolean isTuple() {
return true; 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 @Override
public String toString(int indent) { public String toString(int indent) {
var is = " ".repeat(indent); var is = " ".repeat(indent);
var start = getClass().getSimpleName() + " {\n"; var start = "(" + getIdentifier() + ") {\n";
var kvs = getKeyValuePairs().stream().map(kv -> { var kvs = getKeyValuePairs().stream().map(kv -> {
if (kv.key == null) { if (kv.key() == null) {
return is + " " + kv.value.toString(indent + 1) + "\n"; return is + " " + kv.value().toString(indent + 1) + "\n";
} else { } else {
return is + " " + kv.key + "=" + kv.value.toString(indent + 1) + "\n"; return is + " " + kv.key() + "=" + kv.value().toString(indent + 1) + "\n";
} }
}).collect(Collectors.joining()); }).collect(Collectors.joining());
var end = is + "}"; var end = is + "}";
return start + kvs + end; 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<KeyValue> getKeyValuePairs(); @Override
public int hashCode() {
public abstract List<String> getNames(); return Objects.hash(getKeyNames(), getNodes());
public abstract List<DataStructureNode> getNodes();
@Value
public static class KeyValue {
String key;
DataStructureNode value;
} }
public static class Builder { public static class Builder {
@ -104,11 +105,11 @@ public abstract class TupleNode extends DataStructureNode {
} }
public TupleNode build() { 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( return hasKeys ? TupleNode.of(
entries.stream().map(kv -> kv.key).toList(), entries.stream().map(KeyValue::key).toList(),
entries.stream().map(kv -> kv.value).toList()) : entries.stream().map(KeyValue::value).toList()) :
TupleNode.of(entries.stream().map(kv -> kv.value).toList()); TupleNode.of(entries.stream().map(KeyValue::value).toList());
} }
} }
} }

View file

@ -2,11 +2,10 @@ package io.xpipe.core.data.node;
import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.DataType;
import io.xpipe.core.data.type.ValueType; import io.xpipe.core.data.type.ValueType;
import lombok.EqualsAndHashCode;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@EqualsAndHashCode(callSuper = false)
public abstract class ValueNode extends DataStructureNode { public abstract class ValueNode extends DataStructureNode {
private static final byte[] NULL = new byte[]{0}; private static final byte[] NULL = new byte[]{0};
@ -14,6 +13,24 @@ public abstract class ValueNode extends DataStructureNode {
protected ValueNode() { 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) { public static ValueNode immutable(byte[] data) {
return new ImmutableValueNode(data); return new ImmutableValueNode(data);
} }

View file

@ -27,6 +27,7 @@ public class DataTypeVisitors {
@Override @Override
public void onArray(ArrayType type) { public void onArray(ArrayType type) {
typeConsumer.accept(type); typeConsumer.accept(type);
typeConsumer.accept(type.getSharedType());
} }
@Override @Override

View file

@ -68,11 +68,11 @@ public class TupleType extends DataType {
int counter = 0; int counter = 0;
for (var kv : t.getKeyValuePairs()) { for (var kv : t.getKeyValuePairs()) {
if (!Objects.equals(kv.getKey(), names.get(counter))) { if (!Objects.equals(kv.key(), names.get(counter))) {
return false; return false;
} }
if (!types.get(counter).matches(kv.getValue())) { if (!types.get(counter).matches(kv.value())) {
return false; return false;
} }
counter++; counter++;

View file

@ -3,8 +3,6 @@ package io.xpipe.core.data.typed;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.TupleType;
import java.io.IOException;
public interface TypedDataStreamCallback { public interface TypedDataStreamCallback {
default void onValue(byte[] data) { default void onValue(byte[] data) {
@ -19,7 +17,7 @@ public interface TypedDataStreamCallback {
default void onTupleEnd() { default void onTupleEnd() {
} }
default void onArrayBegin(int size) throws IOException { default void onArrayBegin(int size) {
} }
default void onArrayEnd() { default void onArrayEnd() {

View file

@ -90,9 +90,16 @@ public class TypedDataStreamParser {
parseValue(in, cb); 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 -> { default -> {
GenericDataStreamParser. throw new IllegalStateException("Unexpected type id: " + b);
throw new IllegalStateException("Unexpected value: " + b);
} }
} }
} }
@ -100,14 +107,7 @@ public class TypedDataStreamParser {
private void parseTypedTuple(InputStream in, TypedDataStreamCallback cb, TupleType type) throws IOException { private void parseTypedTuple(InputStream in, TypedDataStreamCallback cb, TupleType type) throws IOException {
cb.onTupleBegin(type); cb.onTupleBegin(type);
for (int i = 0; i < type.getSize(); i++) { for (int i = 0; i < type.getSize(); i++) {
if (type.getTypes().get(i).isWildcard()) { parse(in, cb, type.getTypes().get(i));
var r = getGenericReader();
GenericDataStreamParser.read(in, r);
var node = r.create();
cb.onGenericNode(node);
} else {
parse(in, cb, type.getTypes().get(i));
}
} }
cb.onTupleEnd(); cb.onTupleEnd();
} }
@ -123,14 +123,7 @@ public class TypedDataStreamParser {
var size = in.read(); var size = in.read();
cb.onArrayBegin(size); cb.onArrayBegin(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
if (type.getSharedType().isWildcard()) { parse(in, cb, type.getSharedType());
var r = getGenericReader();
GenericDataStreamParser.read(in, r);
var node = r.create();
cb.onGenericNode(node);
} else {
parse(in, cb, type.getSharedType());
}
} }
cb.onArrayEnd(); cb.onArrayEnd();
} }

View file

@ -28,7 +28,7 @@ public class TypedDataStreamWriter {
} else if (node.isValue() && type.isValue()) { } else if (node.isValue() && type.isValue()) {
writeValue(out, (ValueNode) node); writeValue(out, (ValueNode) node);
} else if (type.isWildcard()) { } else if (type.isWildcard()) {
GenericDataStreamWriter.write(out, node); GenericDataStreamWriter.writeStructure(out, node);
} else { } else {
throw new IllegalStateException("Incompatible node and type"); throw new IllegalStateException("Incompatible node and type");
} }

View file

@ -1,15 +1,10 @@
package io.xpipe.core.data.typed; package io.xpipe.core.data.typed;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.*;
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.type.DataType; 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.DataTypeVisitors;
import io.xpipe.core.data.type.TupleType;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -17,22 +12,6 @@ import java.util.Stack;
public class TypedDataStructureNodeReader implements TypedAbstractReader { public class TypedDataStructureNodeReader implements TypedAbstractReader {
private final List<DataType> flattened;
private final Stack<List<DataStructureNode>> children;
private final Stack<DataStructureNode> 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) { public static TypedDataStructureNodeReader mutable(DataType type) {
return new TypedDataStructureNodeReader(type, false); return new TypedDataStructureNodeReader(type, false);
} }
@ -41,10 +20,30 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
return new TypedDataStructureNodeReader(type, true); return new TypedDataStructureNodeReader(type, true);
} }
private final boolean makeImmutable;
private DataStructureNode readNode;
private final Stack<List<DataStructureNode>> children;
private final Stack<DataStructureNode> nodes;
private int arrayDepth;
private final List<DataType> 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 @Override
public void onNodeBegin() { public void onNodeBegin() {
if (nodes.size() != 0 || children.size() != 0) { if (nodes.size() != 0 || children.size() != 0) {
throw new IllegalStateException(); throw new IllegalStateException("Reader did not completely reset");
} }
readNode = null; readNode = null;
@ -56,30 +55,37 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
} }
public DataStructureNode create() { public DataStructureNode create() {
if (readNode == null) {
throw new IllegalStateException("Reader is not finished yet");
}
return readNode; return readNode;
} }
@Override @Override
public void onNodeEnd() { public void onNodeEnd() {
if (nodes.size() != 0 || children.size() != 0 || readNode == null) { 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 @Override
public void onValue(byte[] data) { public void onValue(byte[] data) {
var val = makeImmutable ? ValueNode.immutable(data) : ValueNode.mutable(data); if (!expectedType.isValue()) {
if (!initialized) { throw new IllegalStateException("Expected " + expectedType.getName() + " but got value");
readNode = val;
return;
} }
children.peek().add(val); var val = makeImmutable ? ValueNode.immutable(data) : ValueNode.mutable(data);
if (!flattened.get(currentDataTypeIndex).isArray()) { finishNode(val);
currentDataTypeIndex++; moveExpectedType(false);
}
} }
private boolean isInArray() { private boolean isInArray() {
@ -88,25 +94,22 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
@Override @Override
public void onGenericNode(DataStructureNode node) { public void onGenericNode(DataStructureNode node) {
children.peek().add(node); if (!expectedType.isWildcard()) {
if (!isInArray()) { throw new IllegalStateException("Expected " + expectedType.getName() + " but got generic node");
currentDataTypeIndex++;
} }
finishNode(makeImmutable ? node.immutableView() : node);
moveExpectedType(false);
} }
@Override @Override
public void onTupleBegin(TupleType type) { public void onTupleBegin(TupleType type) {
if (!isInArray() && !flattened.get(currentDataTypeIndex).isTuple()) { if (!expectedType.isTuple()) {
throw new IllegalStateException(); throw new IllegalStateException("Expected " + expectedType.getName() + " but got tuple");
} }
TupleType tupleType = (TupleType) flattened.get(currentDataTypeIndex); TupleType tupleType = expectedType.asTuple();
if (!initialized || !flattened.get(currentDataTypeIndex).isArray()) { moveExpectedType(false);
currentDataTypeIndex++;
}
if (!initialized) {
initialized = true;
}
var l = new ArrayList<DataStructureNode>(tupleType.getSize()); var l = new ArrayList<DataStructureNode>(tupleType.getSize());
children.push(l); children.push(l);
@ -114,7 +117,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
var tupleNames = makeImmutable ? var tupleNames = makeImmutable ?
Collections.unmodifiableList(tupleType.getNames()) : new ArrayList<>(tupleType.getNames()); Collections.unmodifiableList(tupleType.getNames()) : new ArrayList<>(tupleType.getNames());
var tupleNodes = makeImmutable ? Collections.unmodifiableList(l) : l; var tupleNodes = makeImmutable ? Collections.unmodifiableList(l) : l;
var newNode = TupleNode.ofRaw(tupleNames, tupleNodes); var newNode = TupleNode.of(!makeImmutable, tupleNames, tupleNodes);
nodes.push(newNode); nodes.push(newNode);
} }
@ -123,49 +126,52 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
children.pop(); children.pop();
var popped = nodes.pop(); var popped = nodes.pop();
if (!popped.isTuple()) { if (!popped.isTuple()) {
throw new IllegalStateException(); throw new IllegalStateException("No tuple to end");
} }
SimpleTupleNode tuple = (SimpleTupleNode) popped; TupleNode tuple = popped.asTuple();
if (tuple.getNames().size() != tuple.getNodes().size()) { if (tuple.getKeyNames().size() != tuple.getNodes().size()) {
throw new IllegalStateException(""); throw new IllegalStateException("Tuple node size mismatch");
} }
if (nodes.empty()) { finishNode(popped);
readNode = popped; }
} else {
children.peek().add(popped); private void moveExpectedType(boolean force) {
if (!isInArray() || force) {
currentExpectedTypeIndex++;
expectedType = currentExpectedTypeIndex == flattened.size() ? null : flattened.get(currentExpectedTypeIndex);
} }
} }
@Override @Override
public void onArrayBegin(int size) throws IOException { public void onArrayBegin(int size) {
if (!flattened.get(currentDataTypeIndex).isArray()) { if (!expectedType.isArray()) {
throw new IllegalStateException(); throw new IllegalStateException("Expected " + expectedType.getName() + " but got array");
} }
arrayDepth++;
moveExpectedType(true);
var l = new ArrayList<DataStructureNode>(); var l = new ArrayList<DataStructureNode>();
children.push(l); children.push(l);
var arrayNodes = makeImmutable ? Collections.unmodifiableList(l) : l; var arrayNodes = makeImmutable ? Collections.unmodifiableList(l) : l;
var newNode = ArrayNode.of(arrayNodes); var newNode = ArrayNode.of(arrayNodes);
nodes.push(newNode); nodes.push(newNode);
arrayDepth++;
} }
@Override @Override
public void onArrayEnd() { public void onArrayEnd() {
arrayDepth--;
if (!isInArray()) { if (!isInArray()) {
currentDataTypeIndex++; throw new IllegalStateException("No array to end");
} }
arrayDepth--;
moveExpectedType(true);
children.pop(); children.pop();
var popped = nodes.pop(); var popped = nodes.pop();
if (nodes.empty()) { finishNode(popped);
readNode = popped;
} else {
children.peek().add(popped);
}
} }
} }

View file

@ -3,10 +3,9 @@ package io.xpipe.core.data.typed;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.ValueNode; import io.xpipe.core.data.node.ValueNode;
import io.xpipe.core.data.type.DataType; 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.DataTypeVisitors;
import io.xpipe.core.data.type.TupleType;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
@ -43,7 +42,6 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader
return arrayDepth >= 1; return arrayDepth >= 1;
} }
private boolean initialized() { private boolean initialized() {
return node != null; return node != null;
} }
@ -73,13 +71,25 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader
return; return;
} }
getCurrentParent().set(indices.peek(), node); if (hasParent()) {
getCurrentParent().set(indices.peek(), node);
} else {
this.node = node;
}
if (!indices.isEmpty()) { if (!indices.isEmpty()) {
indices.push(indices.pop() + 1); indices.push(indices.pop() + 1);
} }
} }
private boolean hasParent() {
return indices.size() > 0;
}
private DataStructureNode getCurrentParent() { private DataStructureNode getCurrentParent() {
if (!hasParent()) {
throw new IllegalStateException("No parent available");
}
var current = node; var current = node;
for (var index : indices.subList(0, indices.size() - 1)) { for (var index : indices.subList(0, indices.size() - 1)) {
current = current.at(index); current = current.at(index);
@ -95,15 +105,6 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader
return current; 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 @Override
public void onTupleBegin(TupleType type) { public void onTupleBegin(TupleType type) {
if (!initialized()) { if (!initialized()) {
@ -128,7 +129,7 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader
} }
@Override @Override
public void onArrayBegin(int size) throws IOException { public void onArrayBegin(int size) {
if (!initialized()) { if (!initialized()) {
initialReader.onArrayBegin(size); initialReader.onArrayBegin(size);
return; return;
@ -157,7 +158,6 @@ public class TypedReusableDataStructureNodeReader implements TypedAbstractReader
public void onNodeBegin() { public void onNodeBegin() {
if (!initialized()) { if (!initialized()) {
initialReader.onNodeBegin(); initialReader.onNodeBegin();
return;
} }
} }

View file

@ -79,10 +79,10 @@ public class DataStructureTest {
public void testGenericIo(DataStructureTests.TypedDataset ds) throws IOException { public void testGenericIo(DataStructureTests.TypedDataset ds) throws IOException {
for (var el : ds.nodes) { for (var el : ds.nodes) {
var dataOut = new ByteArrayOutputStream(); var dataOut = new ByteArrayOutputStream();
GenericDataStreamWriter.write(dataOut, el); GenericDataStreamWriter.writeStructure(dataOut, el);
var data = dataOut.toByteArray(); var data = dataOut.toByteArray();
var reader = new GenericDataStructureNodeReader(); var reader = new GenericDataStructureNodeReader();
GenericDataStreamParser.read(new ByteArrayInputStream(data), reader); GenericDataStreamParser.parse(new ByteArrayInputStream(data), reader);
var readNode = reader.create(); var readNode = reader.create();
Assertions.assertEquals(el, readNode); Assertions.assertEquals(el, readNode);

View file

@ -8,6 +8,7 @@ import io.xpipe.core.data.type.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class DataStructureTests { public class DataStructureTests {
@ -144,7 +145,7 @@ public class DataStructureTests {
var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8));
var array = ArrayNode.of(List.of(val, ValueNode.nullValue())); var array = ArrayNode.of(List.of(val, ValueNode.nullValue()));
var tuple = TupleNode.builder() var tuple = TupleNode.builder()
.add("key1", val).add("key2", array).build(); .add(val).add("key2", array).build();
return tuple; return tuple;
} }
@ -153,12 +154,15 @@ public class DataStructureTests {
var flatTuple = TupleNode.builder().add("key1", val).build(); var flatTuple = TupleNode.builder().add("key1", val).build();
var tuple = TupleNode.builder() var tuple = TupleNode.builder()
.add("key1", flatTuple).add("key2", val).build(); .add(flatTuple).add("key2", val).build();
return tuple; return tuple;
} }
public static DataType createTestDataType6() { public static DataType createTestDataType6() {
return TupleType.of(List.of("key1", "key2"), List.of(WildcardType.of(), WildcardType.of())); var keys = new ArrayList<String>();
keys.add(null);
keys.add("key2");
return TupleType.of(keys, List.of(WildcardType.of(), WildcardType.of()));
} }
public static DataStructureNode createTestData71() { public static DataStructureNode createTestData71() {