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 3a1dfe06..321215b7 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java @@ -11,8 +11,10 @@ import io.xpipe.core.data.node.TupleNode; import io.xpipe.core.data.typed.TypedAbstractReader; import io.xpipe.core.data.typed.TypedDataStreamParser; import io.xpipe.core.data.typed.TypedDataStructureNodeReader; -import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader; -import io.xpipe.core.source.*; +import io.xpipe.core.source.DataSourceId; +import io.xpipe.core.source.DataSourceInfo; +import io.xpipe.core.source.DataSourceReference; +import io.xpipe.core.source.DataSourceType; import java.io.InputStream; import java.util.*; @@ -63,7 +65,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable { .ref(DataSourceReference.id(getId())).maxRows(maxRows).build(); con.performInputExchange(req, (QueryTableDataExchange.Response res, InputStream in) -> { var r = new TypedDataStreamParser(info.getDataType()); - r.parseStructures(in, TypedDataStructureNodeReader.immutable(info.getDataType()), nodes::add); + r.parseStructures(in, TypedDataStructureNodeReader.of(info.getDataType()), nodes::add); }); }); return ArrayNode.of(nodes); @@ -78,7 +80,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable { private final TypedAbstractReader nodeReader; { - nodeReader = TypedReusableDataStructureNodeReader.create(info.getDataType()); + nodeReader = TypedDataStructureNodeReader.of(info.getDataType()); parser = new TypedDataStreamParser(info.getDataType()); connection = XPipeConnection.open(); diff --git a/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java b/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java index 745d2c6d..3e9b87bb 100644 --- a/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java +++ b/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java @@ -21,7 +21,7 @@ public class DataTableAccumulatorTest extends DaemonControl { var acc = DataTableAccumulator.create(type); var val = type.convert( - TupleNode.of(List.of(ValueNode.of("val1"), ValueNode.of("val2")))).orElseThrow(); + TupleNode.of(List.of(ValueNode.ofValue("val1"), ValueNode.ofValue("val2")))).orElseThrow(); acc.add(val); var table = acc.finish(":test"); 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 fbb83283..d6d5119d 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 @@ -6,6 +6,7 @@ import io.xpipe.core.data.node.ValueNode; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class GenericArrayReader implements GenericAbstractReader { @@ -127,9 +128,9 @@ public class GenericArrayReader implements GenericAbstractReader { } @Override - public void onValue(byte[] value, boolean textual) { + public void onValue(byte[] value, Map metaAttributes) { if (currentReader != null) { - currentReader.onValue(value, textual); + currentReader.onValue(value, metaAttributes); return; } @@ -141,7 +142,7 @@ public class GenericArrayReader implements GenericAbstractReader { throw new IllegalStateException("Array is full but got another value"); } - put(ValueNode.mutable(value, textual)); + put(ValueNode.of(value).tag(metaAttributes)); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamCallback.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamCallback.java index 8d9f8a1a..7e2a0e19 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamCallback.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamCallback.java @@ -1,5 +1,7 @@ package io.xpipe.core.data.generic; +import java.util.Map; + public interface GenericDataStreamCallback { default void onName(String name) { @@ -17,6 +19,7 @@ public interface GenericDataStreamCallback { default void onTupleEnd() { } - default void onValue(byte[] value, boolean textual) { + + default void onValue(byte[] value, Map metaAttributes) { } } 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 dd77c9d5..8e0aa464 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 @@ -66,6 +66,7 @@ public class GenericDataStreamParser { for (int i = 0; i < size; i++) { parse(in, cb); } + var attributes = DataStructureNodeIO.parseAttributes(in); cb.onTupleEnd(); } @@ -75,19 +76,14 @@ public class GenericDataStreamParser { for (int i = 0; i < size; i++) { parse(in, cb); } + var attributes = DataStructureNodeIO.parseAttributes(in); cb.onArrayEnd(); } private static void parseValue(InputStream in, GenericDataStreamCallback cb) throws IOException { - var type = in.read(); - if (type == DataStructureNodeIO.VALUE_TYPE_NULL) { - cb.onValue(null, false); - return; - } - - var textual = type == DataStructureNodeIO.VALUE_TYPE_TEXT; var size = in.read(); var data = in.readNBytes(size); - cb.onValue(data, textual); + var attributes = DataStructureNodeIO.parseAttributes(in); + cb.onValue(data, attributes); } } 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 e39a01c0..06fa840a 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 @@ -41,6 +41,7 @@ public class GenericDataStreamWriter { writeName(out, tuple.keyNameAt(i)); write(out, tuple.at(i)); } + DataStructureNodeIO.writeAttributes(out, tuple); } private static void writeArray(OutputStream out, ArrayNode array) throws IOException { @@ -49,17 +50,13 @@ public class GenericDataStreamWriter { for (int i = 0; i < array.size(); i++) { write(out, array.at(i)); } + DataStructureNodeIO.writeAttributes(out, array); } private static void writeValue(OutputStream out, ValueNode n) throws IOException { out.write(DataStructureNodeIO.GENERIC_VALUE_ID); - if (n.isNull()) { - out.write(DataStructureNodeIO.VALUE_TYPE_NULL); - return; - } - - out.write(n.isTextual() ? DataStructureNodeIO.VALUE_TYPE_TEXT : DataStructureNodeIO.VALUE_TYPE_BARE); out.write(n.getRawData().length); out.write(n.getRawData()); + DataStructureNodeIO.writeAttributes(out, n); } } 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 4d3a0b29..a33a4968 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 @@ -3,6 +3,8 @@ package io.xpipe.core.data.generic; import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.ValueNode; +import java.util.Map; + public class GenericDataStructureNodeReader implements GenericDataStreamCallback { private DataStructureNode node; @@ -78,12 +80,13 @@ public class GenericDataStructureNodeReader implements GenericDataStreamCallback } @Override - public void onValue(byte[] value, boolean textual) { + public void onValue(byte[] value, Map metaAttributes) { if (hasReader()) { - reader.onValue(value, textual); + reader.onValue(value, metaAttributes); return; } - node = ValueNode.mutable(value, textual); + + node = ValueNode.of(value).tag(metaAttributes); } } 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 31101fd4..375847db 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 @@ -7,6 +7,7 @@ import io.xpipe.core.data.node.ValueNode; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class GenericTupleReader implements GenericAbstractReader { @@ -139,9 +140,9 @@ public class GenericTupleReader implements GenericAbstractReader { } @Override - public void onValue(byte[] value, boolean textual) { + public void onValue(byte[] value, Map metaAttributes) { if (currentReader != null) { - currentReader.onValue(value, textual); + currentReader.onValue(value, metaAttributes); return; } @@ -153,7 +154,7 @@ public class GenericTupleReader implements GenericAbstractReader { throw new IllegalStateException("Tuple is full but got another value"); } - putNode(ValueNode.mutable(value, textual)); + putNode(ValueNode.of(value).tag(metaAttributes)); } @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 d648323b..279c66eb 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 @@ -17,11 +17,7 @@ public abstract class ArrayNode extends DataStructureNode { } public static ArrayNode of(List nodes) { - return new SimpleArrayNode(true, nodes); - } - - public static ArrayNode of(boolean mutable, List nodes) { - return new SimpleArrayNode(mutable, nodes); + return new SimpleArrayNode(nodes); } protected ArrayNode() { @@ -29,8 +25,12 @@ public abstract class ArrayNode extends DataStructureNode { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ArrayNode that)) return false; + if (this == o) { + return true; + } + if (!(o instanceof ArrayNode that)) { + return false; + } return getNodes().equals(that.getNodes()); } @@ -49,18 +49,10 @@ public abstract class ArrayNode extends DataStructureNode { return "array node"; } - @Override - 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 + "]"; + return "[" + content + "]"; } @Override 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 faafc2a1..45b55df9 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 @@ -8,20 +8,70 @@ import java.util.stream.Stream; public abstract class DataStructureNode implements Iterable { - public static final String KEY_TABLE_NAME = "tableName"; - public static final String KEY_ROW_NAME = "rowName"; + public static final Integer KEY_TABLE_NAME = 1; + public static final Integer KEY_ROW_NAME = 2; + public static final Integer BOOLEAN_TRUE = 3; + public static final Integer BOOLEAN_FALSE = 4; + public static final Integer INTEGER_VALUE = 5; + public static final Integer TEXT = 5; - private Properties properties = new Properties(); + private Map metaAttributes; - public String getMetaString(String key) { - if (properties == null) { + public Map getMetaAttributes() { + return metaAttributes != null ? Collections.unmodifiableMap(metaAttributes) : null; + } + + public DataStructureNode tag(Integer key) { + if (metaAttributes == null) { + metaAttributes = new HashMap<>(); + } + + metaAttributes.put(key, null); + return this; + } + + public DataStructureNode tag(Map metaAttributes) { + if (metaAttributes == null) { + return this; + } + + if (this.metaAttributes == null) { + this.metaAttributes = new HashMap<>(); + } + + this.metaAttributes.putAll(metaAttributes); + return this; + } + + public DataStructureNode tag(Integer key, String value) { + if (metaAttributes == null) { + metaAttributes = new HashMap<>(); + } + + metaAttributes.put(key, value); + return this; + } + + + public String getMetaAttribute(Integer key) { + if (metaAttributes == null) { return null; } - return properties.getProperty(key); + return metaAttributes.get(key); } - public abstract DataStructureNode mutableCopy(); + public boolean hasMetaAttribute(Integer key) { + if (metaAttributes == null) { + return false; + } + + return metaAttributes.containsKey(key); + } + + public DataStructureNode mutable() { + return this; + } public String keyNameAt(int index) { throw unsupported("key name at"); @@ -50,8 +100,6 @@ public abstract class DataStructureNode implements Iterable { public abstract boolean isMutable(); - public abstract DataStructureNode immutableView(); - @Override public String toString() { return toString(0); @@ -61,26 +109,6 @@ public abstract class DataStructureNode implements Iterable { throw unsupported("clear"); } - public boolean isTextual() { - throw unsupported("textual check"); - } - - public DataStructureNode setRaw(byte[] data) { - throw unsupported("set raw data"); - } - - public DataStructureNode set(Object newValue) { - throw unsupported("set"); - } - - public DataStructureNode set(Object newValue, boolean textual) { - throw unsupported("set"); - } - - public DataStructureNode set(int index, DataStructureNode node) { - throw unsupported("set at index"); - } - public abstract String toString(int indent); public boolean isTuple() { @@ -99,6 +127,12 @@ public abstract class DataStructureNode implements Iterable { return false; } + + public DataStructureNode set(int index, DataStructureNode node) { + throw unsupported("set at index"); + } + + public final ValueNode asValue() { if (!isValue()) { throw new UnsupportedOperationException(getName() + " is not a value node"); diff --git a/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java b/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java index 28572cde..1776fa90 100644 --- a/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java +++ b/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java @@ -1,5 +1,12 @@ package io.xpipe.core.data.node; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + public class DataStructureNodeIO { public static final int GENERIC_STRUCTURE_ID = 0; @@ -13,7 +20,47 @@ public class DataStructureNodeIO { public static final int TYPED_ARRAY_ID = 7; public static final int TYPED_VALUE_ID = 8; - public static final int VALUE_TYPE_BARE = 0; - public static final int VALUE_TYPE_TEXT = 1; - public static final int VALUE_TYPE_NULL = 2; + public static void writeString(OutputStream out, String s) throws IOException { + if (s != null) { + var b = s.getBytes(StandardCharsets.UTF_8); + out.write(b.length); + out.write(b); + } + } + + public static String parseString(InputStream in) throws IOException { + var nameLength = in.read(); + var name = new String(in.readNBytes(nameLength), StandardCharsets.UTF_8); + return name; + } + + public static Map parseAttributes(InputStream in) throws IOException { + var attributesLength = in.read(); + if (attributesLength == 0) { + return null; + } + + var map = new HashMap(); + for (int i = 0; i < attributesLength; i++) { + var key = in.read(); + var value = parseString(in); + map.put(key, value); + } + return map; + } + + public static void writeAttributes(OutputStream out, DataStructureNode s) throws IOException { + if (s.getMetaAttributes() != null) { + out.write(s.getMetaAttributes().size()); + for (Map.Entry entry : s.getMetaAttributes().entrySet()) { + Integer integer = entry.getKey(); + var value = entry.getValue().getBytes(StandardCharsets.UTF_8); + out.write(integer); + out.write(value.length); + out.write(value); + } + } else { + out.write(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 deleted file mode 100644 index b0c77ebc..00000000 --- a/core/src/main/java/io/xpipe/core/data/node/ImmutableValueNode.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.xpipe.core.data.node; - -public abstract class ImmutableValueNode extends ValueNode { - - @Override - public String toString(int indent) { - return (isTextual() ? "\"" : "") + asString() + (isTextual() ? "\"" : "") + " (I)"; - } - - @Override - public boolean isMutable() { - return false; - } - - @Override - public ValueNode immutableView() { - return this; - } - - @Override - public DataStructureNode setRaw(byte[] data) { - throw new UnsupportedOperationException("Value node is immutable"); - } - - @Override - public DataStructureNode set(Object newValue) { - throw new UnsupportedOperationException("Value node is immutable"); - } - - @Override - public DataStructureNode set(Object newValue, boolean textual) { - throw new UnsupportedOperationException("Value node is immutable"); - } -} diff --git a/core/src/main/java/io/xpipe/core/data/node/LinkedTupleNode.java b/core/src/main/java/io/xpipe/core/data/node/LinkedTupleNode.java index fc7bac69..9a370d7e 100644 --- a/core/src/main/java/io/xpipe/core/data/node/LinkedTupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/LinkedTupleNode.java @@ -169,21 +169,11 @@ public class LinkedTupleNode extends TupleNode { } @Override - protected String getIdentifier() { - return "linked tuple node"; - } - - @Override - public TupleNode mutableCopy() { + public TupleNode mutable() { if (isMutable()) { return this; } - return new LinkedTupleNode(tupleNodes.stream().map(n -> n.isMutable() ? n : n.mutableCopy()).toList()); - } - - @Override - public TupleNode immutableView() { - return this; + return new LinkedTupleNode(tupleNodes.stream().map(n -> n.isMutable() ? n : n.mutable()).toList()); } } 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 deleted file mode 100644 index 6341046b..00000000 --- a/core/src/main/java/io/xpipe/core/data/node/MutableValueNode.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.xpipe.core.data.node; - -import java.nio.charset.StandardCharsets; - -public class MutableValueNode extends ValueNode { - - static final MutableValueNode NULL = new MutableValueNode(null, false); - - private byte[] data; - private boolean textual; - - MutableValueNode(byte[] data, boolean textual) { - this.data = data; - this.textual = textual; - } - - @Override - public String asString() { - return new String(data); - } - - @Override - public String toString(int indent) { - if (isNull()) { - return "null (M)"; - } - - return (textual ? "\"" : "") + new String(data) + (textual ? "\"" : "") + " (M)"; - } - - @Override - public boolean isNull() { - return data == null; - } - - @Override - public boolean isTextual() { - return textual; - } - - @Override - public boolean isMutable() { - return true; - } - - @Override - public ValueNode immutableView() { - return new SimpleImmutableValueNode(data, textual); - } - - @Override - public ValueNode mutableCopy() { - return new MutableValueNode(data, textual); - } - - @Override - public DataStructureNode setRaw(byte[] data) { - this.data = data; - return this; - } - - @Override - public DataStructureNode set(Object newValue) { - if (newValue == null) { - this.data = null; - this.textual = false; - } else { - setRaw(newValue.toString().getBytes(StandardCharsets.UTF_8)); - } - - return this; - } - - @Override - public DataStructureNode set(Object newValue, boolean textual) { - if (newValue == null && textual) { - throw new IllegalArgumentException("Can't set a textual null"); - } - - if (newValue == null) { - this.data = null; - this.textual = false; - } else { - setRaw(newValue.toString().getBytes(StandardCharsets.UTF_8)); - this.textual = textual; - } - - 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 deleted file mode 100644 index 9a4e3a47..00000000 --- a/core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java +++ /dev/null @@ -1,86 +0,0 @@ -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.stream.Collectors; - -public class NoKeyTupleNode extends TupleNode { - - private final boolean mutable; - private final List 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(nodes.stream().map(DataStructureNode::determineDataType).toList()); - } - - @Override - protected String getName() { - return "no key tuple node"; - } - - @Override - public boolean isMutable() { - return mutable; - } - - @Override - public DataStructureNode at(int index) { - return nodes.get(index); - } - - @Override - public int size() { - return nodes.size(); - } - - @Override - public List getKeyValuePairs() { - return nodes.stream().map(n -> new KeyValue(null, n)).toList(); - } - - @Override - public List getKeyNames() { - return Collections.nCopies(size(), null); - } - - public List getNodes() { - 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 index 950af038..5b4a1a1a 100644 --- a/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java @@ -1,38 +1,29 @@ package io.xpipe.core.data.node; -import java.util.*; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.Stream; +@Value +@EqualsAndHashCode(callSuper = true) 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"); - } - } + List nodes; @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; } @@ -47,34 +38,13 @@ public class SimpleArrayNode extends ArrayNode { 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))); + return true; } @Override public DataStructureNode clear() { - checkMutable(); - nodes.clear(); return this; } @@ -101,13 +71,11 @@ public class SimpleArrayNode extends ArrayNode { @Override public List getNodes() { - return nodes; + return Collections.unmodifiableList(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/SimpleImmutableValueNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java index 69ac842b..e6225692 100644 --- a/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java @@ -1,25 +1,15 @@ package io.xpipe.core.data.node; +import lombok.NonNull; + import java.nio.charset.StandardCharsets; -public class SimpleImmutableValueNode extends ImmutableValueNode { +public class SimpleImmutableValueNode extends ValueNode { - private final byte[] data; - private final boolean textual; + private final byte @NonNull [] data; - SimpleImmutableValueNode(byte[] data, boolean textual) { + SimpleImmutableValueNode(byte @NonNull [] data) { this.data = data; - this.textual = textual; - } - - @Override - public ValueNode mutableCopy() { - return ValueNode.mutable(data, textual); - } - - @Override - public boolean isTextual() { - return textual; } public byte[] getRawData() { @@ -28,10 +18,20 @@ public class SimpleImmutableValueNode extends ImmutableValueNode { @Override public final String asString() { - if (getRawData() == null) { + if (getRawData().length == 0 && !hasMetaAttribute(TEXT)) { return "null"; } return new String(getRawData(), StandardCharsets.UTF_8); } + + @Override + public String toString(int indent) { + return (hasMetaAttribute(TEXT) ? "\"" : "") + asString() + (hasMetaAttribute(TEXT) ? "\"" : "") + " (I)"; + } + + @Override + public boolean isMutable() { + return false; + } } 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 06344acf..67ac937d 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 @@ -3,41 +3,23 @@ package io.xpipe.core.data.node; import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.TupleType; -import java.util.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + public class SimpleTupleNode extends TupleNode { - private final boolean mutable; private final List names; private final List 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); + public SimpleTupleNode(List names, List nodes) { + this.names = names; + this.nodes = 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; } @@ -54,7 +36,7 @@ public class SimpleTupleNode extends TupleNode { @Override public boolean isMutable() { - return mutable; + return true; } @Override @@ -83,8 +65,6 @@ public class SimpleTupleNode extends TupleNode { @Override public DataStructureNode clear() { - checkMutable(); - nodes.clear(); names.clear(); return this; @@ -118,7 +98,7 @@ public class SimpleTupleNode extends TupleNode { } @Override - protected String getIdentifier() { - return "S"; + public TupleNode mutable() { + return this; } } 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 14c44fdd..e57d59e3 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 @@ -16,7 +16,7 @@ public abstract class TupleNode extends DataStructureNode { throw new IllegalArgumentException("Nodes must be not null"); } - return new NoKeyTupleNode(true, nodes); + return new SimpleTupleNode(null, nodes); } public static TupleNode of(List names, List nodes) { @@ -30,41 +30,20 @@ public abstract class TupleNode extends DataStructureNode { throw new IllegalArgumentException("Names and nodes must have the same length"); } - return new SimpleTupleNode(true, names, nodes); + return new SimpleTupleNode(names, 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(mutable, names, nodes); - } + @Override + public abstract TupleNode mutable(); 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 = "(" + getIdentifier() + ") {\n"; + var start = "{\n"; var kvs = getKeyValuePairs().stream().map(kv -> { if (kv.key() == null) { return is + " " + kv.value().toString(indent + 1) + "\n"; 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 9fca269b..f6e1f955 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 @@ -5,17 +5,23 @@ import io.xpipe.core.data.type.ValueType; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Objects; 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()); + if (this == o) { + return true; + } + if (!(o instanceof ValueNode that)) { + return false; + } + return Arrays.equals(getRawData(), that.getRawData()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes()); } @Override @@ -23,68 +29,18 @@ public abstract class ValueNode extends DataStructureNode { return Arrays.hashCode(getRawData()); } - @Override - public abstract ValueNode immutableView(); - - @Override - public abstract ValueNode mutableCopy(); - - public static ValueNode immutable(byte[] data, boolean textual) { - return new SimpleImmutableValueNode(data, textual); - } - - public static ValueNode immutable(Object o, boolean textual) { - if (o == null) { - return immutableNull(); - } - - return immutable(o.toString().getBytes(StandardCharsets.UTF_8), textual); - } - - public static ValueNode immutableNull() { - return MutableValueNode.NULL.immutableView(); - } - - public static ValueNode mutableNull() { - return MutableValueNode.NULL.mutableCopy(); - } - - public static ValueNode mutable(byte[] data, boolean textual) { - return new MutableValueNode(data, textual); - } - - public static ValueNode mutable(Object o, boolean textual) { - return mutable(o.toString().getBytes(StandardCharsets.UTF_8), textual); + public static ValueNode nullValue() { + return new SimpleImmutableValueNode(new byte[0]); } public static ValueNode of(byte[] data) { - return mutable(data, false); + return new SimpleImmutableValueNode(data); } public static ValueNode of(Object o) { - return mutable(o, false); + return of(o.toString().getBytes(StandardCharsets.UTF_8)); } - public static ValueNode ofText(byte[] data) { - return mutable(data, true); - } - - public static ValueNode ofText(Object o) { - return mutable(o, true); - } - - @Override - public abstract boolean isTextual(); - - @Override - public abstract DataStructureNode setRaw(byte[] data); - - @Override - public abstract DataStructureNode set(Object newValue); - - @Override - public abstract DataStructureNode set(Object newValue, boolean textual); - @Override public final int asInt() { return Integer.parseInt(asString()); @@ -107,9 +63,4 @@ public abstract class ValueNode extends DataStructureNode { public abstract byte[] getRawData(); - @Override - public boolean isNull() { - return getRawData() == null; - } - } 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 26dbc748..f1e9b68d 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 @@ -5,7 +5,7 @@ import io.xpipe.core.data.type.TupleType; public interface TypedDataStreamCallback { - default void onValue(byte[] data, boolean textual) { + default void onValue(byte[] data) { } default void onGenericNode(DataStructureNode node) { 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 7d4a01d1..87e3010c 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 @@ -129,15 +129,8 @@ public class TypedDataStreamParser { } private void parseValue(InputStream in, TypedDataStreamCallback cb) throws IOException { - var type = in.read(); - if (type == DataStructureNodeIO.VALUE_TYPE_NULL) { - cb.onValue(null, false); - return; - } - - var textual = type == DataStructureNodeIO.VALUE_TYPE_TEXT; var size = in.read(); var data = in.readNBytes(size); - cb.onValue(data, textual); + cb.onValue(data); } } 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 6b543645..c749e624 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 @@ -32,12 +32,6 @@ public class TypedDataStreamWriter { private static void writeValue(OutputStream out, ValueNode n) throws IOException { out.write(DataStructureNodeIO.TYPED_VALUE_ID); - if (n.isNull()) { - out.write(DataStructureNodeIO.VALUE_TYPE_NULL); - return; - } - - out.write(n.isTextual() ? DataStructureNodeIO.VALUE_TYPE_TEXT : DataStructureNodeIO.VALUE_TYPE_BARE); out.write(n.getRawData().length); out.write(n.getRawData()); } 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 3f747a70..76ed7cc9 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 @@ -6,21 +6,15 @@ import io.xpipe.core.data.type.DataTypeVisitors; import io.xpipe.core.data.type.TupleType; 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 of(DataType type) { + return new TypedDataStructureNodeReader(type); } - public static TypedDataStructureNodeReader immutable(DataType type) { - return new TypedDataStructureNodeReader(type, true); - } - - private final boolean makeImmutable; private DataStructureNode readNode; private final Stack> children; @@ -31,12 +25,11 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { private DataType expectedType; private int currentExpectedTypeIndex; - private TypedDataStructureNodeReader(DataType type, boolean makeImmutable) { + private TypedDataStructureNodeReader(DataType type) { flattened = new ArrayList<>(); type.visit(DataTypeVisitors.flatten(flattened::add)); children = new Stack<>(); nodes = new Stack<>(); - this.makeImmutable = makeImmutable; expectedType = flattened.get(0); } @@ -81,12 +74,12 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { } @Override - public void onValue(byte[] data, boolean textual) { + public void onValue(byte[] data) { if (!expectedType.isValue()) { throw new IllegalStateException("Expected " + expectedType.getName() + " but got value"); } - var val = makeImmutable ? ValueNode.immutable(data, textual) : ValueNode.mutable(data, textual); + var val = ValueNode.of(data); finishNode(val); moveExpectedType(false); } @@ -101,7 +94,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { throw new IllegalStateException("Expected " + expectedType.getName() + " but got generic node"); } - finishNode(makeImmutable ? node.immutableView() : node); + finishNode(node); moveExpectedType(false); } @@ -117,10 +110,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { var l = new ArrayList(tupleType.getSize()); children.push(l); - var tupleNames = makeImmutable ? - Collections.unmodifiableList(tupleType.getNames()) : new ArrayList<>(tupleType.getNames()); - var tupleNodes = makeImmutable ? Collections.unmodifiableList(l) : l; - var newNode = TupleNode.of(!makeImmutable, tupleNames, tupleNodes); + var newNode = new SimpleTupleNode(tupleType.getNames(), l); nodes.push(newNode); } @@ -159,8 +149,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader { var l = new ArrayList(); children.push(l); - var arrayNodes = makeImmutable ? Collections.unmodifiableList(l) : l; - var newNode = ArrayNode.of(arrayNodes); + var newNode = ArrayNode.of(l); nodes.push(newNode); } 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 deleted file mode 100644 index edb22499..00000000 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedReusableDataStructureNodeReader.java +++ /dev/null @@ -1,171 +0,0 @@ -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.DataTypeVisitors; -import io.xpipe.core.data.type.TupleType; - -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -public class TypedReusableDataStructureNodeReader implements TypedAbstractReader { - - public static TypedReusableDataStructureNodeReader create(DataType type) { - return new TypedReusableDataStructureNodeReader(type); - } - - private final List flattened; - private final TypedDataStructureNodeReader initialReader; - private DataStructureNode node; - private final Stack indices; - private int arrayDepth; - - private TypedReusableDataStructureNodeReader(DataType type) { - flattened = new ArrayList<>(); - indices = new Stack<>(); - initialReader = TypedDataStructureNodeReader.mutable(type); - type.visit(DataTypeVisitors.flatten(d -> flattened.add(d))); - } - - @Override - public boolean isDone() { - return true; - } - - public DataStructureNode create() { - return node; - } - - private boolean isInArray() { - return arrayDepth >= 1; - } - - private boolean initialized() { - return node != null; - } - - @Override - public void onValue(byte[] data, boolean textual) { - if (!initialized()) { - initialReader.onValue(data, textual); - return; - } - - if (isInArray()) { - getCurrentParent().set(indices.peek(), ValueNode.mutable(data, textual)); - } else { - getCurrent().setRaw(data); - } - - if (!indices.isEmpty()) { - indices.push(indices.pop() + 1); - } - } - - @Override - public void onGenericNode(DataStructureNode node) { - if (!initialized()) { - initialReader.onGenericNode(node); - return; - } - - 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); - } - return current; - } - - private DataStructureNode getCurrent() { - var current = node; - for (var index : indices) { - current = current.at(index); - } - return current; - } - - @Override - public void onTupleBegin(TupleType type) { - if (!initialized()) { - initialReader.onTupleBegin(type); - return; - } - - indices.push(0); - } - - @Override - public void onTupleEnd() { - if (!initialized()) { - initialReader.onTupleEnd(); - return; - } - - indices.pop(); - if (!indices.isEmpty()) { - indices.push(indices.pop() + 1); - } - } - - @Override - public void onArrayBegin(int size) { - if (!initialized()) { - initialReader.onArrayBegin(size); - return; - } - - getCurrent().clear(); - indices.push(0); - arrayDepth++; - } - - @Override - public void onArrayEnd() { - if (!initialized()) { - initialReader.onArrayEnd(); - return; - } - - indices.pop(); - arrayDepth--; - if (!indices.isEmpty()) { - indices.push(indices.pop() + 1); - } - } - - @Override - public void onNodeBegin() { - if (!initialized()) { - initialReader.onNodeBegin(); - } - } - - @Override - public void onNodeEnd() { - if (!initialized()) { - initialReader.onNodeEnd(); - node = initialReader.create(); - } - } -} diff --git a/core/src/main/java/io/xpipe/core/store/DataStore.java b/core/src/main/java/io/xpipe/core/store/DataStore.java index 6416c4f4..45156550 100644 --- a/core/src/main/java/io/xpipe/core/store/DataStore.java +++ b/core/src/main/java/io/xpipe/core/store/DataStore.java @@ -16,6 +16,9 @@ import java.util.Optional; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface DataStore { + default DataStoreFlow getFlow() { + return DataStoreFlow.INOUT; + } /** * Checks whether this store can be opened. diff --git a/core/src/main/java/io/xpipe/core/store/DataStoreFlow.java b/core/src/main/java/io/xpipe/core/store/DataStoreFlow.java new file mode 100644 index 00000000..525c630f --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/DataStoreFlow.java @@ -0,0 +1,8 @@ +package io.xpipe.core.store; + +public enum DataStoreFlow { + + INPUT, + OUTPUT, + INOUT +} diff --git a/core/src/main/java/io/xpipe/core/store/ShellTypes.java b/core/src/main/java/io/xpipe/core/store/ShellTypes.java index da38f763..4f9b87a8 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellTypes.java +++ b/core/src/main/java/io/xpipe/core/store/ShellTypes.java @@ -124,7 +124,8 @@ public class ShellTypes { @Override public Charset getCharset() { - return StandardCharsets.UTF_16LE; + // TODO + return StandardCharsets.ISO_8859_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 2766e262..0c62daaf 100644 --- a/core/src/test/java/io/xpipe/core/test/DataStructureTest.java +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTest.java @@ -1,16 +1,15 @@ package io.xpipe.core.test; -import io.xpipe.core.data.node.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.DataStructureNode; 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; @@ -19,7 +18,6 @@ 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 { @@ -91,74 +89,20 @@ public class DataStructureTest { @ParameterizedTest @EnumSource(DataStructureTests.TypedDataset.class) - public void testMutableTypedIo(DataStructureTests.TypedDataset ds) throws IOException { + public void testTypedIo(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); + var reader = TypedDataStructureNodeReader.of(ds.type); new TypedDataStreamParser(ds.type).parseStructure(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).parseStructure(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.setRaw("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 = TypedReusableDataStructureNodeReader.create(ds.type); - - for (var node : ds.nodes) { - new TypedDataStreamParser(ds.type).parseStructure(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 496ecb8a..a74c5a79 100644 --- a/core/src/test/java/io/xpipe/core/test/DataStructureTests.java +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTests.java @@ -58,7 +58,7 @@ public class DataStructureTests { } private static DataStructureNode createTestData12() { - var val = ValueNode.of(null); + var val = ValueNode.nullValue(); var flatArray = ArrayNode.of(); var flatTuple = TupleNode.builder().add("key1", val).build(); var nestedArray = ArrayNode.of(List.of(flatArray, flatTuple)); @@ -101,7 +101,7 @@ public class DataStructureTests { public static DataStructureNode createTestData32() { var val = ValueNode.of("value2".getBytes(StandardCharsets.UTF_8)); - var flatTuple = TupleNode.builder().add("key1", ValueNode.of(null)).add("key2", ValueNode.of(null)).build(); + var flatTuple = TupleNode.builder().add("key1", ValueNode.nullValue()).add("key2", ValueNode.nullValue()).build(); var flatArray = ArrayNode.of(List.of(val, flatTuple)); return flatArray; } @@ -112,13 +112,13 @@ public class DataStructureTests { } public static DataStructureNode createTestData42() { - var val = ValueNode.of(null); + 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.of(null))); + 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; @@ -143,7 +143,7 @@ public class DataStructureTests { public static DataStructureNode createTestData61() { var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); - var array = ArrayNode.of(List.of(val, ValueNode.of(null))); + var array = ArrayNode.of(List.of(val, ValueNode.nullValue())); var tuple = TupleNode.builder() .add(val).add("key2", array).build(); return tuple; @@ -176,7 +176,7 @@ public class DataStructureTests { public static DataStructureNode createTestData73() { var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8)); - var array = ArrayNode.of(List.of(val, ValueNode.of(null))); + var array = ArrayNode.of(List.of(val, ValueNode.nullValue())); return array; } diff --git a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java index 380f5b12..69478499 100644 --- a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java @@ -1,10 +1,7 @@ package io.xpipe.extension; import io.xpipe.core.dialog.Dialog; -import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.MachineFileStore; -import io.xpipe.core.store.ShellStore; -import io.xpipe.core.store.StreamDataStore; +import io.xpipe.core.store.*; import javafx.beans.property.Property; import java.util.List; @@ -91,4 +88,8 @@ public interface DataStoreProvider { } List> getStoreClasses(); + + default DataStoreFlow[] getPossibleFlows() { + return new DataStoreFlow[]{DataStoreFlow.INPUT}; + } } diff --git a/extension/src/main/java/io/xpipe/extension/DialogHelper.java b/extension/src/main/java/io/xpipe/extension/DialogHelper.java index 5582f833..d9fc2e37 100644 --- a/extension/src/main/java/io/xpipe/extension/DialogHelper.java +++ b/extension/src/main/java/io/xpipe/extension/DialogHelper.java @@ -4,13 +4,13 @@ import io.xpipe.core.charsetter.NewLine; import io.xpipe.core.charsetter.StreamCharset; import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.QueryConverter; -import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.LocalStore; -import io.xpipe.core.store.MachineFileStore; -import io.xpipe.core.store.ShellStore; +import io.xpipe.core.source.DataSource; +import io.xpipe.core.store.*; import io.xpipe.core.util.Secret; import lombok.Value; +import java.util.function.Predicate; + public class DialogHelper { @Value @@ -50,6 +50,10 @@ public class DialogHelper { }); } + public static Dialog dataStoreFlowQuery(DataStoreFlow flow, DataStoreFlow[] available) { + return Dialog.choice("flow", o -> o.toString(), true, flow, available); + } + public static Dialog shellQuery(DataStore store) { var storeName = XPipeDaemon.getInstance() .getStoreName(store) @@ -111,6 +115,22 @@ public class DialogHelper { }); } + public static Dialog sourceQuery(DataSource source, Predicate> filter) { + var id = XPipeDaemon.getInstance() + .getSourceId(source) + .orElse(null); + return Dialog.query("Source Id", false, true, false, id, QueryConverter.STRING) + .map((String newName) -> { + var found = XPipeDaemon.getInstance() + .getSource(newName) + .orElseThrow(); + if (!filter.test(found)) { + throw new IllegalArgumentException("Incompatible store type"); + } + return found; + }); + } + public static Dialog passwordQuery(Secret password) { return Dialog.querySecret("Password", false, true, password); } diff --git a/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java b/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java index 4545604e..7b0f5d94 100644 --- a/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java +++ b/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java @@ -53,5 +53,9 @@ public interface SupportedApplicationProvider { default String getGraphicIcon() { return null; } + + default boolean isApplicable(DataSource source) { + return true; + } } diff --git a/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java b/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java index b8ef8e57..b3a77fdd 100644 --- a/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java +++ b/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java @@ -1,5 +1,6 @@ package io.xpipe.extension; +import io.xpipe.core.source.DataSource; import io.xpipe.core.store.DataStore; import io.xpipe.fxcomps.Comp; import javafx.beans.property.Property; @@ -25,9 +26,15 @@ public interface XPipeDaemon { Comp namedStoreChooser(ObservableValue> filter, Property selected, DataStoreProvider.Category category); + Comp namedSourceChooser(ObservableValue>> filter, Property> selected, DataSourceProvider.Category category); + Comp sourceProviderChooser(Property> provider, DataSourceProvider.Category category); Optional getNamedStore(String name); + Optional> getSource(String id); + Optional getStoreName(DataStore store); + + Optional getSourceId(DataSource source); } diff --git a/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java new file mode 100644 index 00000000..0c02bd11 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java @@ -0,0 +1,33 @@ +package io.xpipe.extension.comp; + +import io.xpipe.core.store.DataStoreFlow; +import io.xpipe.extension.I18n; +import io.xpipe.fxcomps.SimpleComp; +import javafx.beans.property.Property; +import javafx.beans.value.ObservableValue; +import javafx.scene.layout.Region; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.util.LinkedHashMap; + +@Value +@EqualsAndHashCode(callSuper = true) +public class DataStoreFlowChoiceComp extends SimpleComp { + + Property selected; + DataStoreFlow[] available; + + @Override + protected Region createSimple() { + var map = new LinkedHashMap>(); + map.put(DataStoreFlow.INPUT, I18n.observable("extension.input")); + map.put(DataStoreFlow.OUTPUT, I18n.observable("extension.output")); + map.put(DataStoreFlow.INOUT, I18n.observable("extension.inout")); + return new ToggleGroupComp<>(selected, map).apply(struc -> { + new FancyTooltipAugment<>("extension.inputDescription").augment(struc.get().getChildren().get(0)); + new FancyTooltipAugment<>("extension.outputDescription").augment(struc.get().getChildren().get(1)); + new FancyTooltipAugment<>("extension.inoutDescription").augment(struc.get().getChildren().get(2)); + }).createRegion(); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java index b96211a9..44200343 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java +++ b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java @@ -149,6 +149,10 @@ public class DynamicOptionsBuilder { return this; } + public DynamicOptionsBuilder addComp(String nameKey, Comp comp, Property prop) { + return addComp(I18n.observable(nameKey), comp, prop); + } + public DynamicOptionsBuilder addComp(ObservableValue name, Comp comp, Property prop) { entries.add(new DynamicOptionsComp.Entry(name, comp)); props.add(prop); diff --git a/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java b/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java new file mode 100644 index 00000000..ce18b0b7 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java @@ -0,0 +1,80 @@ +package io.xpipe.extension.comp; + +import com.jfoenix.controls.JFXTooltip; +import io.xpipe.extension.I18n; +import io.xpipe.fxcomps.CompStructure; +import io.xpipe.fxcomps.Shortcuts; +import io.xpipe.fxcomps.augment.Augment; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.Node; +import javafx.scene.layout.Region; +import javafx.stage.Window; +import javafx.util.Duration; + +import java.util.function.Supplier; + +public class FancyTooltipAugment> implements Augment { + + static { + JFXTooltip.setHoverDelay(Duration.millis(400)); + JFXTooltip.setVisibleDuration(Duration.INDEFINITE); + } + + private final ObservableValue text; + + public FancyTooltipAugment(Supplier text) { + this.text = new SimpleObjectProperty<>(text.get()); + } + + public FancyTooltipAugment(String key) { + this.text = I18n.observable(key); + } + + @Override + public void augment(S struc) { + var tt = new FocusTooltip(); + var toDisplay = text.getValue(); + if (Shortcuts.getShortcut(struc.get()) != null) { + toDisplay = toDisplay + " (" + Shortcuts.getShortcut(struc.get()).getDisplayText() + ")"; + } + tt.textProperty().setValue(toDisplay); + tt.setStyle("-fx-font-size: 11pt;"); + JFXTooltip.install(struc.get(), tt); + tt.setWrapText(true); + tt.setMaxWidth(400); + tt.getStyleClass().add("fancy-tooltip"); + } + + + public void augment(Node region) { + var tt = new FocusTooltip(); + var toDisplay = text.getValue(); + if (Shortcuts.getShortcut((Region) region) != null) { + toDisplay = toDisplay + " (" + Shortcuts.getShortcut((Region) region).getDisplayText() + ")"; + } + tt.textProperty().setValue(toDisplay); + tt.setStyle("-fx-font-size: 11pt;"); + JFXTooltip.install(region, tt); + tt.setWrapText(true); + tt.setMaxWidth(400); + tt.getStyleClass().add("fancy-tooltip"); + } + + private static class FocusTooltip extends JFXTooltip { + + public FocusTooltip() { + } + + public FocusTooltip(String string) { + super(string); + } + + @Override + protected void show() { + Window owner = getOwnerWindow(); + if (owner.isFocused()) + super.show(); + } + } +} diff --git a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties index 576f3552..00df05f6 100644 --- a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties +++ b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties @@ -9,4 +9,10 @@ null=$VALUE$ must be not null hostFeatureUnsupported=Host does not support the feature $FEATURE$ namedHostFeatureUnsupported=$HOST$ does not support this feature namedHostNotActive=$HOST$ is not active -noInformationAvailable=No information available \ No newline at end of file +noInformationAvailable=No information available +input=Input +output=Output +inout=In/Out +inputDescription=This store only produces input for data sources to read +outputDescription=This store only accepts output from data sources to write +inoutDescription=This store uses both input and output to essentially create a data transformation \ No newline at end of file