From 8a36027a3140e050fb0390ab573644841f517e38 Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Wed, 15 Dec 2021 01:50:00 +0100 Subject: [PATCH] Rework everything --- api/build.gradle | 34 ++-- api/src/main/java/io/xpipe/api/DataTable.java | 6 +- .../java/io/xpipe/api/XPipeApiConnector.java | 8 +- .../io/xpipe/api/XPipeClientException.java | 19 ++ .../io/xpipe/api/XPipeConnectException.java | 19 ++ .../java/io/xpipe/api/XPipeException.java | 23 --- .../io/xpipe/api/XPipeServerException.java | 19 ++ .../java/io/xpipe/api/impl/DataTableImpl.java | 26 +-- .../java/io/xpipe/api/test/XPipeConfig.java | 3 +- core/build.gradle | 19 ++ .../io/xpipe/core/data/DataStructureNode.java | 9 +- .../xpipe/core/data/DataStructureNodeIO.java | 10 ++ .../DataStructureNodePointer.java | 4 +- .../xpipe/core/data/generic/ArrayReader.java | 55 ------ .../core/data/generic/DataStreamCallback.java | 30 ---- .../core/data/generic/DataStreamWriter.java | 33 ---- .../data/generic/DataStructureReader.java | 69 ------- .../core/data/generic/GenericArrayReader.java | 161 +++++++++++++++++ .../generic/GenericDataStreamCallback.java | 21 +++ ...ader.java => GenericDataStreamReader.java} | 33 ++-- .../data/generic/GenericDataStreamWriter.java | 60 +++++++ ...va => GenericDataStructureNodeReader.java} | 2 +- .../generic/GenericDataStructureReader.java | 84 +++++++++ .../core/data/generic/GenericTupleReader.java | 168 ++++++++++++++++++ .../xpipe/core/data/generic/TupleReader.java | 119 ------------- .../data/{generic => node}/ArrayNode.java | 15 +- .../xpipe/core/data/node/NoKeyTupleNode.java | 65 +++++++ .../SimpleTupleNode.java} | 34 ++-- .../io/xpipe/core/data/node/TupleNode.java | 115 ++++++++++++ .../data/{generic => node}/ValueNode.java | 15 +- .../data/type/callback/DataTypeCallbacks.java | 2 +- .../data/type/callback/TableTypeCallback.java | 2 +- ...eusableTypedDataStructureNodeCallback.java | 2 +- .../TypedDataStreamCallback.java | 2 +- .../TypedDataStreamReader.java | 12 +- .../TypedDataStreamWriter.java | 12 +- .../TypedDataStructureNodeReader.java} | 92 ++++++---- ...aSource.java => DataSourceDescriptor.java} | 2 +- .../io/xpipe/core/source/DataSourceId.java | 57 ++++-- .../io/xpipe/core/source/DataSourceType.java | 5 +- .../core/source/DataStructureConnection.java | 5 - .../io/xpipe/core/source/DataTableSource.java | 15 -- .../core/source/DataTableWriteConnection.java | 12 -- .../source/StructureDataReadConnection.java | 5 + ...ava => StructureDataSourceDescriptor.java} | 2 +- ...tion.java => TableDataReadConnection.java} | 8 +- .../source/TableDataSourceDescriptor.java | 15 ++ .../core/source/TableDataWriteConnection.java | 12 ++ ...{FileDataInput.java => FileDataStore.java} | 6 +- ...DataInput.java => LocalFileDataStore.java} | 8 +- ...ataInput.java => RemoteFileDataStore.java} | 6 +- .../io/xpipe/core/util/CoreJacksonModule.java | 4 +- core/src/main/java/module-info.java | 6 + .../io/xpipe/core/test/DataSourceIdTest.java | 62 +++++++ .../io/xpipe/core/test/DataStructureTest.java | 56 ++++++ .../xpipe/core/test/DataStructureTests.java | 25 +++ .../core/test/GenericDataStructureIoTest.java | 35 ++++ .../core/test/TypedDataStructureIoTest.java | 36 ++++ core/src/test/java/module-info.java | 7 + .../extension/DataSourceGuiProvider.java | 10 +- .../xpipe/extension/DataSourceProvider.java | 4 +- .../xpipe/extension/DataSourceProviders.java | 2 - 62 files changed, 1272 insertions(+), 535 deletions(-) create mode 100644 api/src/main/java/io/xpipe/api/XPipeClientException.java create mode 100644 api/src/main/java/io/xpipe/api/XPipeConnectException.java delete mode 100644 api/src/main/java/io/xpipe/api/XPipeException.java create mode 100644 api/src/main/java/io/xpipe/api/XPipeServerException.java create mode 100644 core/src/main/java/io/xpipe/core/data/DataStructureNodeIO.java rename core/src/main/java/io/xpipe/core/data/{generic => }/DataStructureNodePointer.java (98%) delete mode 100644 core/src/main/java/io/xpipe/core/data/generic/ArrayReader.java delete mode 100644 core/src/main/java/io/xpipe/core/data/generic/DataStreamCallback.java delete mode 100644 core/src/main/java/io/xpipe/core/data/generic/DataStreamWriter.java delete mode 100644 core/src/main/java/io/xpipe/core/data/generic/DataStructureReader.java create mode 100644 core/src/main/java/io/xpipe/core/data/generic/GenericArrayReader.java create mode 100644 core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamCallback.java rename core/src/main/java/io/xpipe/core/data/generic/{DataStreamReader.java => GenericDataStreamReader.java} (52%) create mode 100644 core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java rename core/src/main/java/io/xpipe/core/data/generic/{DataStructureNodeReader.java => GenericDataStructureNodeReader.java} (62%) create mode 100644 core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureReader.java create mode 100644 core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java delete mode 100644 core/src/main/java/io/xpipe/core/data/generic/TupleReader.java rename core/src/main/java/io/xpipe/core/data/{generic => node}/ArrayNode.java (78%) create mode 100644 core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java rename core/src/main/java/io/xpipe/core/data/{generic/TupleNode.java => node/SimpleTupleNode.java} (68%) create mode 100644 core/src/main/java/io/xpipe/core/data/node/TupleNode.java rename core/src/main/java/io/xpipe/core/data/{generic => node}/ValueNode.java (69%) rename core/src/main/java/io/xpipe/core/data/{type/callback => typed}/ReusableTypedDataStructureNodeCallback.java (93%) rename core/src/main/java/io/xpipe/core/data/{type/callback => typed}/TypedDataStreamCallback.java (89%) rename core/src/main/java/io/xpipe/core/data/{type => typed}/TypedDataStreamReader.java (90%) rename core/src/main/java/io/xpipe/core/data/{type => typed}/TypedDataStreamWriter.java (82%) rename core/src/main/java/io/xpipe/core/data/{type/callback/TypedDataStructureNodeCallback.java => typed/TypedDataStructureNodeReader.java} (59%) rename core/src/main/java/io/xpipe/core/source/{DataSource.java => DataSourceDescriptor.java} (78%) delete mode 100644 core/src/main/java/io/xpipe/core/source/DataStructureConnection.java delete mode 100644 core/src/main/java/io/xpipe/core/source/DataTableSource.java delete mode 100644 core/src/main/java/io/xpipe/core/source/DataTableWriteConnection.java create mode 100644 core/src/main/java/io/xpipe/core/source/StructureDataReadConnection.java rename core/src/main/java/io/xpipe/core/source/{DataStructureSource.java => StructureDataSourceDescriptor.java} (70%) rename core/src/main/java/io/xpipe/core/source/{DataTableConnection.java => TableDataReadConnection.java} (60%) create mode 100644 core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java create mode 100644 core/src/main/java/io/xpipe/core/source/TableDataWriteConnection.java rename core/src/main/java/io/xpipe/core/store/{FileDataInput.java => FileDataStore.java} (56%) rename core/src/main/java/io/xpipe/core/store/{LocalFileDataInput.java => LocalFileDataStore.java} (88%) rename core/src/main/java/io/xpipe/core/store/{RemoteFileDataInput.java => RemoteFileDataStore.java} (84%) create mode 100644 core/src/test/java/io/xpipe/core/test/DataSourceIdTest.java create mode 100644 core/src/test/java/io/xpipe/core/test/DataStructureTest.java create mode 100644 core/src/test/java/io/xpipe/core/test/DataStructureTests.java create mode 100644 core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java create mode 100644 core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java create mode 100644 core/src/test/java/module-info.java diff --git a/api/build.gradle b/api/build.gradle index 2e5412d7f..a347c1dbe 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -4,26 +4,13 @@ plugins { } java { - modularity.inferModulePath = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } repositories { mavenCentral() -} - - -apply from: "$rootDir/deps/commons.gradle" -apply from: "$rootDir/deps/jackson.gradle" - -dependencies { - implementation project(':core') - implementation project(':beacon') - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' - //testImplementation project(':app') - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + maven { url "https://jitpack.io" } } plugins.withType(JavaPlugin).configureEach { @@ -32,8 +19,25 @@ plugins.withType(JavaPlugin).configureEach { } } +apply from: "$rootDir/deps/commons.gradle" +apply from: "$rootDir/deps/jackson.gradle" + +dependencies { + implementation project(':core') + implementation project(':beacon') + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testRuntimeOnly project(':app') + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + test { useJUnitPlatform() + testLogging { + exceptionFormat = 'full' + showStandardStreams = true + } //workingDir = project(":app").projectDir @@ -41,4 +45,6 @@ test { systemProperty "io.xpipe.daemon.mode", 'base' systemProperty "io.xpipe.storage.dir", "$projectDir/test_env" systemProperty "io.xpipe.beacon.port", "21722" + systemProperty 'io.xpipe.app.writeSysOut', "true" + systemProperty 'io.xpipe.app.logLevel', "debug" } diff --git a/api/src/main/java/io/xpipe/api/DataTable.java b/api/src/main/java/io/xpipe/api/DataTable.java index ec4cd8cba..948578856 100644 --- a/api/src/main/java/io/xpipe/api/DataTable.java +++ b/api/src/main/java/io/xpipe/api/DataTable.java @@ -1,14 +1,14 @@ package io.xpipe.api; import io.xpipe.api.impl.DataTableImpl; -import io.xpipe.core.data.generic.ArrayNode; -import io.xpipe.core.data.generic.TupleNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.SimpleTupleNode; import io.xpipe.core.data.type.DataType; import io.xpipe.core.source.DataSourceId; import java.util.OptionalInt; -public interface DataTable extends Iterable { +public interface DataTable extends Iterable { static DataTable get(String s) { return DataTableImpl.get(s); diff --git a/api/src/main/java/io/xpipe/api/XPipeApiConnector.java b/api/src/main/java/io/xpipe/api/XPipeApiConnector.java index 8ebf2e4a4..5bcbbe0f2 100644 --- a/api/src/main/java/io/xpipe/api/XPipeApiConnector.java +++ b/api/src/main/java/io/xpipe/api/XPipeApiConnector.java @@ -10,13 +10,13 @@ public abstract class XPipeApiConnector extends BeaconConnector { var socket = constructSocket(); handle(socket); } catch (ConnectorException ce) { - throw new XPipeException("Connection error: " + ce.getMessage()); + throw new XPipeConnectException(ce.getMessage()); } catch (ClientException ce) { - throw new XPipeException("Client error: " + ce.getMessage()); + throw new XPipeClientException(ce.getMessage()); } catch (ServerException se) { - throw new XPipeException("Server error: " + se.getMessage()); + throw new XPipeServerException(se.getMessage()); } catch (Throwable t) { - throw new XPipeException("Unexpected error", t); + throw new XPipeConnectException(t); } } diff --git a/api/src/main/java/io/xpipe/api/XPipeClientException.java b/api/src/main/java/io/xpipe/api/XPipeClientException.java new file mode 100644 index 000000000..ed376dcff --- /dev/null +++ b/api/src/main/java/io/xpipe/api/XPipeClientException.java @@ -0,0 +1,19 @@ +package io.xpipe.api; + +public class XPipeClientException extends RuntimeException { + + public XPipeClientException() { + } + + public XPipeClientException(String message) { + super(message); + } + + public XPipeClientException(String message, Throwable cause) { + super(message, cause); + } + + public XPipeClientException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/io/xpipe/api/XPipeConnectException.java b/api/src/main/java/io/xpipe/api/XPipeConnectException.java new file mode 100644 index 000000000..90ed51048 --- /dev/null +++ b/api/src/main/java/io/xpipe/api/XPipeConnectException.java @@ -0,0 +1,19 @@ +package io.xpipe.api; + +public class XPipeConnectException extends RuntimeException { + + public XPipeConnectException() { + } + + public XPipeConnectException(String message) { + super(message); + } + + public XPipeConnectException(String message, Throwable cause) { + super(message, cause); + } + + public XPipeConnectException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/io/xpipe/api/XPipeException.java b/api/src/main/java/io/xpipe/api/XPipeException.java deleted file mode 100644 index d5b62bebd..000000000 --- a/api/src/main/java/io/xpipe/api/XPipeException.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.xpipe.api; - -public class XPipeException extends RuntimeException { - - public XPipeException() { - } - - public XPipeException(String message) { - super(message); - } - - public XPipeException(String message, Throwable cause) { - super(message, cause); - } - - public XPipeException(Throwable cause) { - super(cause); - } - - public XPipeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/api/src/main/java/io/xpipe/api/XPipeServerException.java b/api/src/main/java/io/xpipe/api/XPipeServerException.java new file mode 100644 index 000000000..137e5bbbf --- /dev/null +++ b/api/src/main/java/io/xpipe/api/XPipeServerException.java @@ -0,0 +1,19 @@ +package io.xpipe.api; + +public class XPipeServerException extends RuntimeException { + + public XPipeServerException() { + } + + public XPipeServerException(String message) { + super(message); + } + + public XPipeServerException(String message, Throwable cause) { + super(message, cause); + } + + public XPipeServerException(Throwable cause) { + super(cause); + } +} 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 19fd970e6..07456aad7 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java @@ -9,12 +9,12 @@ import io.xpipe.beacon.BeaconClient; import io.xpipe.beacon.exchange.ReadTableDataExchange; import io.xpipe.beacon.exchange.ReadTableInfoExchange; import io.xpipe.core.data.DataStructureNode; -import io.xpipe.core.data.generic.ArrayNode; -import io.xpipe.core.data.generic.TupleNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.SimpleTupleNode; import io.xpipe.core.data.type.DataType; -import io.xpipe.core.data.type.TypedDataStreamReader; -import io.xpipe.core.data.type.callback.TypedDataStreamCallback; -import io.xpipe.core.data.type.callback.TypedDataStructureNodeCallback; +import io.xpipe.core.data.typed.TypedDataStreamReader; +import io.xpipe.core.data.typed.TypedDataStreamCallback; +import io.xpipe.core.data.typed.TypedDataStructureNodeReader; import io.xpipe.core.source.DataSourceId; import java.io.IOException; @@ -51,7 +51,7 @@ public class DataTableImpl implements DataTable { this.dataType = dataType; } - public Stream stream() { + public Stream stream() { return StreamSupport.stream( Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED), false); } @@ -95,7 +95,7 @@ public class DataTableImpl implements DataTable { protected void handle(BeaconClient sc) throws ClientException, ServerException, ConnectorException { var req = new ReadTableDataExchange.Request(id, maxToRead); performExchange(sc, req, (ReadTableDataExchange.Response res, InputStream in) -> { - TypedDataStreamReader.readStructures(in, new TypedDataStructureNodeCallback(dataType, nodes::add)); + TypedDataStreamReader.readStructures(in, new TypedDataStructureNodeReader(dataType, nodes::add)); }, false); } }.execute(); @@ -103,14 +103,14 @@ public class DataTableImpl implements DataTable { } @Override - public Iterator iterator() { - return new Iterator() { + public Iterator iterator() { + return new Iterator() { private InputStream input; private int read; private final int toRead = size; private TypedDataStreamCallback callback; - private TupleNode current; + private SimpleTupleNode current; { new XPipeApiConnector() { @@ -123,8 +123,8 @@ public class DataTableImpl implements DataTable { } }.execute(); - callback = new TypedDataStructureNodeCallback(dataType, dsn -> { - current = (TupleNode) dsn; + callback = new TypedDataStructureNodeReader(dataType, dsn -> { + current = (SimpleTupleNode) dsn; }); } @@ -150,7 +150,7 @@ public class DataTableImpl implements DataTable { } @Override - public TupleNode next() { + public SimpleTupleNode next() { try { TypedDataStreamReader.readStructure(input, callback); } catch (IOException ex) { diff --git a/api/src/test/java/io/xpipe/api/test/XPipeConfig.java b/api/src/test/java/io/xpipe/api/test/XPipeConfig.java index b8328c15d..5d5f045a4 100644 --- a/api/src/test/java/io/xpipe/api/test/XPipeConfig.java +++ b/api/src/test/java/io/xpipe/api/test/XPipeConfig.java @@ -1,6 +1,5 @@ package io.xpipe.api.test; -import io.xpipe.beacon.BeaconServer; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -17,7 +16,7 @@ public class XPipeConfig implements BeforeAllCallback, ExtensionContext.Store.Cl // Your "before all tests" startup logic goes here // The following line registers a callback hook when the root test context is shut down context.getRoot().getStore(GLOBAL).put("any unique name", this); - BeaconServer.start(); + //BeaconServer.start(); } } diff --git a/core/build.gradle b/core/build.gradle index 16f96070f..134d256c6 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -15,3 +15,22 @@ repositories { apply from: "$rootDir/deps/commons.gradle" apply from: "$rootDir/deps/jackson.gradle" + +dependencies { + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + + +test { + useJUnitPlatform() + testLogging { + exceptionFormat = 'full' + showStandardStreams = true + } +} diff --git a/core/src/main/java/io/xpipe/core/data/DataStructureNode.java b/core/src/main/java/io/xpipe/core/data/DataStructureNode.java index 2122d3629..8e88601a6 100644 --- a/core/src/main/java/io/xpipe/core/data/DataStructureNode.java +++ b/core/src/main/java/io/xpipe/core/data/DataStructureNode.java @@ -12,10 +12,17 @@ public abstract class DataStructureNode implements Iterable { protected abstract String getName(); - private UnsupportedOperationException unuspported(String s) { + protected UnsupportedOperationException unuspported(String s) { return new UnsupportedOperationException(getName() + " does not support " + s); } + @Override + public String toString() { + return toString(0); + } + + public abstract String toString(int indent); + public boolean isTuple() { return false; } diff --git a/core/src/main/java/io/xpipe/core/data/DataStructureNodeIO.java b/core/src/main/java/io/xpipe/core/data/DataStructureNodeIO.java new file mode 100644 index 000000000..ca6864980 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/DataStructureNodeIO.java @@ -0,0 +1,10 @@ +package io.xpipe.core.data; + +public class DataStructureNodeIO { + + public static final int STRUCTURE_ID = 0; + public static final int TUPLE_ID = 1; + public static final int ARRAY_ID = 2; + public static final int VALUE_ID = 3; + public static final int NAME_ID = 4; +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/DataStructureNodePointer.java b/core/src/main/java/io/xpipe/core/data/DataStructureNodePointer.java similarity index 98% rename from core/src/main/java/io/xpipe/core/data/generic/DataStructureNodePointer.java rename to core/src/main/java/io/xpipe/core/data/DataStructureNodePointer.java index 066436c1a..50636a664 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/DataStructureNodePointer.java +++ b/core/src/main/java/io/xpipe/core/data/DataStructureNodePointer.java @@ -1,6 +1,4 @@ -package io.xpipe.core.data.generic; - -import io.xpipe.core.data.DataStructureNode; +package io.xpipe.core.data; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/io/xpipe/core/data/generic/ArrayReader.java b/core/src/main/java/io/xpipe/core/data/generic/ArrayReader.java deleted file mode 100644 index 12271985e..000000000 --- a/core/src/main/java/io/xpipe/core/data/generic/ArrayReader.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.xpipe.core.data.generic; - -import io.xpipe.core.data.DataStructureNode; - -import java.util.ArrayList; -import java.util.List; - -public class ArrayReader implements DataStructureNodeReader { - - private final List nodes; - private int length; - private boolean hasSeenEnd; - private int currentIndex = 0; - private DataStructureNodeReader currentReader; - - public ArrayReader(int length) { - this.length = length; - this.nodes = new ArrayList<>(length); - } - - @Override - public void onArrayStart(String name, int length) { - DataStructureNodeReader.super.onArrayStart(name, length); - } - - @Override - public void onArrayEnd() { - DataStructureNodeReader.super.onArrayEnd(); - } - - @Override - public void onTupleStart(String name, int length) { - DataStructureNodeReader.super.onTupleStart(name, length); - } - - @Override - public void onTupleEnd() { - DataStructureNodeReader.super.onTupleEnd(); - } - - @Override - public void onValue(String name, byte[] value) { - DataStructureNodeReader.super.onValue(name, value); - } - - @Override - public boolean isDone() { - return false; - } - - @Override - public DataStructureNode create() { - return null; - } -} diff --git a/core/src/main/java/io/xpipe/core/data/generic/DataStreamCallback.java b/core/src/main/java/io/xpipe/core/data/generic/DataStreamCallback.java deleted file mode 100644 index 05cbd8a26..000000000 --- a/core/src/main/java/io/xpipe/core/data/generic/DataStreamCallback.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.xpipe.core.data.generic; - -import java.util.function.Consumer; - -public interface DataStreamCallback { - - static DataStreamCallback flat(Consumer con) { - return new DataStreamCallback() { - @Override - public void onValue(String name, byte[] value) { - con.accept(value); - } - }; - } - - default void onArrayStart(String name, int length) { - } - - default void onArrayEnd() { - } - - default void onTupleStart(String name, int length) { - } - - default void onTupleEnd() { - } - - default void onValue(String name, byte[] value) { - } -} diff --git a/core/src/main/java/io/xpipe/core/data/generic/DataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/generic/DataStreamWriter.java deleted file mode 100644 index 78ccbfd7e..000000000 --- a/core/src/main/java/io/xpipe/core/data/generic/DataStreamWriter.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.xpipe.core.data.generic; - -import io.xpipe.core.data.DataStructureNode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - -public class DataStreamWriter { - - private static final int TUPLE_ID = 1; - private static final int ARRAY_ID = 2; - private static final int VALUE_ID = 3; - - public static void write(OutputStream out, DataStructureNode node) throws IOException { - if (node.isTuple()) { - writeTuple(out, (TupleNode) node); - } - } - - private static void writeName(OutputStream out, String s) throws IOException { - out.write(s.length()); - out.write(s.getBytes(StandardCharsets.UTF_8)); - } - - private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException { - out.write(TUPLE_ID); - for (int i = 0; i < tuple.size(); i++) { - writeName(out, tuple.nameAt(i)); - write(out, tuple.at(i)); - } - } -} diff --git a/core/src/main/java/io/xpipe/core/data/generic/DataStructureReader.java b/core/src/main/java/io/xpipe/core/data/generic/DataStructureReader.java deleted file mode 100644 index ad4bf9cb9..000000000 --- a/core/src/main/java/io/xpipe/core/data/generic/DataStructureReader.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.xpipe.core.data.generic; - -import io.xpipe.core.data.DataStructureNode; - -public class DataStructureReader implements DataStreamCallback { - - private boolean isWrapped; - private DataStructureNodeReader reader; - - public DataStructureNode create() { - return null; - } - - - @Override - public void onArrayStart(String name, int length) { - if (reader != null) { - reader.onArrayStart(name, length); - return; - } - - if (name != null) { - reader = new TupleReader(1); - reader.onArrayStart(name, length); - } else { - reader = new ArrayReader(length); - reader.onArrayStart(null, length); - } - } - - @Override - public void onArrayEnd() { - if (reader != null) { - reader.onArrayEnd(); - } - } - - @Override - public void onTupleStart(String name, int length) { - if (reader != null) { - reader.onTupleStart(name, length); - return; - } - - if (name != null) { - reader = new TupleReader(1); - reader.onTupleStart(name, length); - } else { - reader = new TupleReader(length); - } - } - - @Override - public void onTupleEnd() { - if (reader != null) { - reader.onTupleEnd(); - if (reader.isDone()) { - - } - } - - DataStreamCallback.super.onTupleEnd(); - } - - @Override - public void onValue(String name, byte[] value) { - DataStreamCallback.super.onValue(name, value); - } -} 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 new file mode 100644 index 000000000..0edc81459 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericArrayReader.java @@ -0,0 +1,161 @@ +package io.xpipe.core.data.generic; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.ValueNode; + +import java.util.ArrayList; +import java.util.List; + +public class GenericArrayReader implements GenericDataStructureNodeReader { + + public static GenericArrayReader newReader(int length) { + var ar = new GenericArrayReader(); + ar.onArrayStart(length); + return ar; + } + + private boolean initialized; + private List nodes; + private int length; + private int currentIndex = 0; + private GenericDataStructureNodeReader currentReader; + private DataStructureNode created; + + public GenericArrayReader() { + } + + private void init(int length) { + this.length = length; + this.nodes = new ArrayList<>(length); + initialized = true; + } + + private void put(DataStructureNode node) { + this.nodes.add(node); + currentIndex++; + } + + @Override + public void onName(String name) { + if (hasReader()) { + currentReader.onName(name); + return; + } + + throw new IllegalStateException("Expected array content but got a key name"); + } + + private boolean filled() { + return currentIndex == length; + } + + private boolean isInitialized() { + return initialized; + } + + private boolean hasReader() { + return currentReader != null; + } + + @Override + public void onArrayStart(int length) { + if (hasReader()) { + currentReader.onArrayStart(length); + return; + } + + if (!isInitialized()) { + init(length); + return; + } + + currentReader = newReader(length); + } + + @Override + public void onArrayEnd() { + if (hasReader()) { + currentReader.onArrayEnd(); + if (currentReader.isDone()) { + put(currentReader.create()); + currentReader = null; + } + return; + } + + if (!isInitialized()) { + throw new IllegalStateException("Expected array start but got array end"); + } + + if (!filled()) { + throw new IllegalStateException("Array ended but is not full yet"); + } + + created = ArrayNode.wrap(nodes); + } + + @Override + public void onTupleStart(int length) { + if (hasReader()) { + currentReader.onTupleStart(length); + return; + } + + if (!isInitialized()) { + throw new IllegalStateException("Expected array start but got tuple start"); + } + + if (filled()) { + throw new IllegalStateException("Tuple is full but got another tuple"); + } + + currentReader = GenericTupleReader.newReader(length); + } + + @Override + public void onTupleEnd() { + if (hasReader()) { + currentReader.onTupleEnd(); + if (currentReader.isDone()) { + put(currentReader.create()); + currentReader = null; + } + return; + } + + throw new IllegalStateException("Expected array end but got tuple end"); + } + + @Override + public void onValue(byte[] value) { + if (currentReader != null) { + currentReader.onValue(value); + return; + } + + if (!isInitialized()) { + throw new IllegalStateException("Expected array start but got value"); + } + + if (filled()) { + throw new IllegalStateException("Array is full but got another value"); + } + + put(ValueNode.wrap(value)); + } + + @Override + public boolean isDone() { + return filled() && created != null; + } + + @Override + public DataStructureNode create() { + if (!isDone()) { + throw new IllegalStateException(); + } + + return ArrayNode.wrap(nodes); + } +} 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 new file mode 100644 index 000000000..1ce49a29e --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamCallback.java @@ -0,0 +1,21 @@ +package io.xpipe.core.data.generic; + +public interface GenericDataStreamCallback { + + default void onName(String name) {} + + default void onArrayStart(int length) { + } + + default void onArrayEnd() { + } + + default void onTupleStart(int length) { + } + + default void onTupleEnd() { + } + + default void onValue(byte[] value) { + } +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/DataStreamReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamReader.java similarity index 52% rename from core/src/main/java/io/xpipe/core/data/generic/DataStreamReader.java rename to core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamReader.java index c462db87b..949ddb436 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/DataStreamReader.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamReader.java @@ -3,14 +3,19 @@ package io.xpipe.core.data.generic; import java.io.IOException; import java.io.InputStream; -public class DataStreamReader { +public class GenericDataStreamReader { private static final int TUPLE_ID = 1; private static final int ARRAY_ID = 2; private static final int VALUE_ID = 3; + private static final int NAME_ID = 4; - public static void read(InputStream in, DataStreamCallback cb) throws IOException { + public static void read(InputStream in, GenericDataStreamCallback cb) throws IOException { var b = in.read(); + if (b == -1) { + return; + } + switch (b) { case TUPLE_ID -> { readTuple(in, cb); @@ -21,39 +26,41 @@ public class DataStreamReader { case VALUE_ID -> { readValue(in, cb); } + case NAME_ID -> { + readName(in, cb); + read(in, cb); + } default -> throw new IllegalStateException("Unexpected value: " + b); } } - private static String readName(InputStream in) throws IOException { + private static void readName(InputStream in, GenericDataStreamCallback cb) throws IOException { var nameLength = in.read(); - return new String(in.readNBytes(nameLength)); + var name = new String(in.readNBytes(nameLength)); + cb.onName(name); } - private static void readTuple(InputStream in, DataStreamCallback cb) throws IOException { - var name = readName(in); + private static void readTuple(InputStream in, GenericDataStreamCallback cb) throws IOException { var size = in.read(); - cb.onTupleStart(name, size); + cb.onTupleStart(size); for (int i = 0; i < size; i++) { read(in, cb); } cb.onTupleEnd(); } - private static void readArray(InputStream in, DataStreamCallback cb) throws IOException { - var name = readName(in); + private static void readArray(InputStream in, GenericDataStreamCallback cb) throws IOException { var size = in.read(); - cb.onArrayStart(name, size); + cb.onArrayStart(size); for (int i = 0; i < size; i++) { read(in, cb); } cb.onArrayEnd(); } - private static void readValue(InputStream in, DataStreamCallback cb) throws IOException { - var name = readName(in); + private static void readValue(InputStream in, GenericDataStreamCallback cb) throws IOException { var size = in.read(); var data = in.readNBytes(size); - cb.onValue(name, data); + cb.onValue(data); } } diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java new file mode 100644 index 000000000..fcacbb510 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java @@ -0,0 +1,60 @@ +package io.xpipe.core.data.generic; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.TupleNode; +import io.xpipe.core.data.node.ValueNode; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class GenericDataStreamWriter { + + private static final int TUPLE_ID = 1; + private static final int ARRAY_ID = 2; + private static final int VALUE_ID = 3; + private static final int NAME_ID = 4; + + public static void write(OutputStream out, DataStructureNode node) throws IOException { + if (node.isTuple()) { + writeTuple(out, (TupleNode) node); + } else if (node.isArray()) { + writeArray(out, (ArrayNode) node); + } else if (node.isValue()) { + writeValue(out, (ValueNode) node); + } else { + throw new IllegalStateException(); + } + } + + private static void writeName(OutputStream out, String s) throws IOException { + var b = s.getBytes(StandardCharsets.UTF_8); + out.write(NAME_ID); + out.write(b.length); + out.write(b); + } + + private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException { + out.write(TUPLE_ID); + out.write(tuple.size()); + for (int i = 0; i < tuple.size(); i++) { + writeName(out, tuple.nameAt(i)); + write(out, tuple.at(i)); + } + } + + private static void writeArray(OutputStream out, ArrayNode array) throws IOException { + out.write(ARRAY_ID); + out.write(array.size()); + for (int i = 0; i < array.size(); i++) { + write(out, array.at(i)); + } + } + + private static void writeValue(OutputStream out, ValueNode value) throws IOException { + out.write(VALUE_ID); + out.write(value.getRawData().length); + out.write(value.getRawData()); + } +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/DataStructureNodeReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java similarity index 62% rename from core/src/main/java/io/xpipe/core/data/generic/DataStructureNodeReader.java rename to core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java index 8bcf8a727..30707b9a7 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/DataStructureNodeReader.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureNodeReader.java @@ -2,7 +2,7 @@ package io.xpipe.core.data.generic; import io.xpipe.core.data.DataStructureNode; -public interface DataStructureNodeReader extends DataStreamCallback { +public interface GenericDataStructureNodeReader extends GenericDataStreamCallback { boolean isDone(); diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureReader.java new file mode 100644 index 000000000..4f374c29b --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStructureReader.java @@ -0,0 +1,84 @@ +package io.xpipe.core.data.generic; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.node.ValueNode; + +public class GenericDataStructureReader implements GenericDataStreamCallback { + + private DataStructureNode node; + private GenericDataStructureNodeReader reader; + + public DataStructureNode create() { + if (node == null) { + throw new IllegalStateException("No node has been created yet"); + } + + return node; + } + + private boolean hasReader() { + return reader != null; + } + + @Override + public void onName(String name) { + if (hasReader()) { + reader.onName(name); + return; + } + + throw new IllegalStateException("Expected node start but got name"); + } + + @Override + public void onArrayStart(int length) { + if (hasReader()) { + reader.onArrayStart(length); + return; + } + + reader = GenericArrayReader.newReader(length); + } + + @Override + public void onArrayEnd() { + if (!hasReader()) { + throw new IllegalStateException("No array to close"); + } + + reader.onArrayEnd(); + } + + @Override + public void onTupleStart(int length) { + if (hasReader()) { + reader.onTupleStart(length); + return; + } + + reader = GenericTupleReader.newReader(length); + } + + @Override + public void onTupleEnd() { + if (!hasReader()) { + throw new IllegalStateException("No tuple to close"); + } + + reader.onTupleEnd(); + if (reader.isDone()) { + node = reader.create(); + reader = null; + } + } + + @Override + public void onValue(byte[] value) { + if (hasReader()) { + reader.onValue(value); + return; + } + + node = ValueNode.wrap(value); + } +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java b/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java new file mode 100644 index 000000000..96bba5650 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericTupleReader.java @@ -0,0 +1,168 @@ +package io.xpipe.core.data.generic; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.node.TupleNode; +import io.xpipe.core.data.node.ValueNode; +import io.xpipe.core.data.node.SimpleTupleNode; + +import java.util.ArrayList; +import java.util.List; + +public class GenericTupleReader implements GenericDataStructureNodeReader { + + public static GenericTupleReader newReader(int length) { + var tr = new GenericTupleReader(); + tr.onTupleStart(length); + return tr; + } + + private boolean initialized; + private int length; + private List names; + private List nodes; + private int currentIndex = 0; + private GenericDataStructureNodeReader currentReader; + private DataStructureNode created; + + public GenericTupleReader() { + } + + private boolean hasReader() { + return currentReader != null; + } + + private void init(int length) { + this.length = length; + this.names = new ArrayList<>(length); + this.nodes = new ArrayList<>(length); + initialized = true; + } + + private boolean isInitialized() { + return initialized; + } + + private void putNode(DataStructureNode node) { + this.nodes.add(node); + currentIndex++; + } + + private boolean filled() { + return currentIndex == length; + } + + @Override + public void onName(String name) { + if (hasReader()) { + currentReader.onName(name); + return; + } + + if (filled()) { + throw new IllegalStateException("Tuple is full but got another name"); + } + + if (names.size() - nodes.size() == 1) { + throw new IllegalStateException("Tuple is waiting for a node but got another name"); + } + + names.add(name); + } + + @Override + public void onArrayStart(int length) { + if (hasReader()) { + currentReader.onArrayStart(length); + return; + } + + if (!isInitialized()) { + throw new IllegalStateException("Expected tuple start but got array start"); + } + + currentReader = GenericArrayReader.newReader(length); + } + + @Override + public void onArrayEnd() { + if (hasReader()) { + currentReader.onArrayEnd(); + if (currentReader.isDone()) { + putNode(currentReader.create()); + currentReader = null; + } + return; + } + + throw new IllegalStateException("Expected tuple end but got array end"); + } + + @Override + public void onTupleStart(int length) { + if (hasReader()) { + currentReader.onTupleStart(length); + return; + } + + if (!isInitialized()) { + init(length); + return; + } + + currentReader = GenericTupleReader.newReader(length); + } + + @Override + public void onTupleEnd() { + if (hasReader()) { + currentReader.onTupleEnd(); + if (currentReader.isDone()) { + putNode(currentReader.create()); + currentReader = null; + } + return; + } + + if (!isInitialized()) { + throw new IllegalStateException("Expected tuple start but got tuple end"); + } + + if (!filled()) { + throw new IllegalStateException("Tuple ended but is not full yet"); + } + + created = TupleNode.wrap(names, nodes); + } + + @Override + public void onValue(byte[] value) { + if (currentReader != null) { + currentReader.onValue(value); + return; + } + + if (!isInitialized()) { + throw new IllegalStateException("Expected tuple start but got value"); + } + + if (filled()) { + throw new IllegalStateException("Tuple is full but got another value"); + } + + putNode(ValueNode.wrap(value)); + } + + @Override + public boolean isDone() { + return filled() && created != null; + } + + @Override + public DataStructureNode create() { + if (!isDone()) { + throw new IllegalStateException(); + } + + return SimpleTupleNode.wrap(names, nodes); + } +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/TupleReader.java b/core/src/main/java/io/xpipe/core/data/generic/TupleReader.java deleted file mode 100644 index e6801a492..000000000 --- a/core/src/main/java/io/xpipe/core/data/generic/TupleReader.java +++ /dev/null @@ -1,119 +0,0 @@ -package io.xpipe.core.data.generic; - -import io.xpipe.core.data.DataStructureNode; - -import java.util.ArrayList; -import java.util.List; - -public class TupleReader implements DataStructureNodeReader { - - private final int length; - private final List names; - private final List nodes; - private boolean hasSeenEnd; - private int currentIndex = 0; - private DataStructureNodeReader currentReader; - - public TupleReader(int length) { - this.length = length; - this.names = new ArrayList<>(length); - this.nodes = new ArrayList<>(length); - } - - private void put(String name, DataStructureNode node) { - this.names.add(name); - this.nodes.add(node); - currentIndex++; - } - - private void putNode(DataStructureNode node) { - this.nodes.add(node); - currentIndex++; - } - - private boolean filled() { - return currentIndex == length; - } - - @Override - public void onArrayStart(String name, int length) { - if (currentReader != null) { - currentReader.onArrayStart(name, length); - return; - } - - names.add(name); - currentReader = new ArrayReader(length); - } - - @Override - public void onArrayEnd() { - if (currentReader != null) { - currentReader.onArrayEnd(); - if (currentReader.isDone()) { - putNode(currentReader.create()); - currentReader = null; - } - return; - } - - throw new IllegalStateException(); - } - - @Override - public void onTupleStart(String name, int length) { - if (currentReader != null) { - currentReader.onTupleStart(name, length); - return; - } - - names.add(name); - currentReader = new TupleReader(length); - } - - @Override - public void onTupleEnd() { - if (currentReader != null) { - currentReader.onTupleEnd(); - if (currentReader.isDone()) { - putNode(currentReader.create()); - currentReader = null; - } - return; - } - - if (!filled()) { - throw new IllegalStateException(); - } - - hasSeenEnd = true; - } - - @Override - public void onValue(String name, byte[] value) { - if (currentReader != null) { - currentReader.onValue(name, value); - return; - } - - if (filled()) { - throw new IllegalStateException(); - } - - put(name, ValueNode.wrap(value)); - } - - @Override - public boolean isDone() { - return filled() && hasSeenEnd; - } - - @Override - public DataStructureNode create() { - if (!isDone()) { - throw new IllegalStateException(); - } - - return TupleNode.wrap(names, nodes); - } -} diff --git a/core/src/main/java/io/xpipe/core/data/generic/ArrayNode.java b/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java similarity index 78% rename from core/src/main/java/io/xpipe/core/data/generic/ArrayNode.java rename to core/src/main/java/io/xpipe/core/data/node/ArrayNode.java index 89bdf72ca..3234a1a80 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/ArrayNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java @@ -1,13 +1,16 @@ -package io.xpipe.core.data.generic; +package io.xpipe.core.data.node; import io.xpipe.core.data.DataStructureNode; import io.xpipe.core.data.type.ArrayType; import io.xpipe.core.data.type.DataType; +import lombok.EqualsAndHashCode; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; +@EqualsAndHashCode(callSuper = false) public class ArrayNode extends DataStructureNode { private final List valueNodes; @@ -16,6 +19,10 @@ public class ArrayNode extends DataStructureNode { this.valueNodes = valueNodes; } + public static ArrayNode of(DataStructureNode... dsn) { + return wrap(List.of(dsn)); + } + public static ArrayNode wrap(List valueNodes) { return new ArrayNode(valueNodes); } @@ -44,6 +51,12 @@ public class ArrayNode extends DataStructureNode { return "array node"; } + @Override + public String toString(int indent) { + var content = valueNodes.stream().map(n -> n.toString(indent)).collect(Collectors.joining(", ")); + return "[" + content + "]"; + } + @Override public DataType getDataType() { return ArrayType.of(valueNodes.stream().map(DataStructureNode::getDataType).toList()); 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 new file mode 100644 index 000000000..6daaa4101 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/node/NoKeyTupleNode.java @@ -0,0 +1,65 @@ +package io.xpipe.core.data.node; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.type.DataType; +import io.xpipe.core.data.type.TupleType; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class NoKeyTupleNode extends TupleNode { + + private final List nodes; + + NoKeyTupleNode(List nodes) { + this.nodes = nodes; + } + + @Override + public DataType getDataType() { + return TupleType.wrap(null, nodes.stream().map(DataStructureNode::getDataType).toList()); + } + + @Override + protected String getName() { + return "no key tuple node"; + } + + @Override + public DataStructureNode at(int index) { + return nodes.get(index); + } + + @Override + public DataStructureNode forKey(String name) { + throw unuspported("key indexing"); + } + + @Override + public Optional forKeyIfPresent(String name) { + return Optional.empty(); + } + + @Override + public int size() { + return nodes.size(); + } + + public String nameAt(int index) { + throw unuspported("name getter"); + } + + @Override + public List getKeyValuePairs() { + return nodes.stream().map(n -> new KeyValue(null, n)).toList(); + } + + public List getNames() { + return Collections.nCopies(size(), null); + } + + public List getNodes() { + return Collections.unmodifiableList(nodes); + } +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/TupleNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java similarity index 68% rename from core/src/main/java/io/xpipe/core/data/generic/TupleNode.java rename to core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java index 7b27ea248..e5f123054 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/TupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java @@ -1,36 +1,23 @@ -package io.xpipe.core.data.generic; +package io.xpipe.core.data.node; import io.xpipe.core.data.DataStructureNode; import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.TupleType; +import lombok.EqualsAndHashCode; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; -public class TupleNode extends DataStructureNode { +@EqualsAndHashCode(callSuper = false) +public class SimpleTupleNode extends TupleNode { private final List names; private final List nodes; - private TupleNode(List names, List nodes) { + SimpleTupleNode(List names, List nodes) { this.names = names; this.nodes = nodes; } - public static TupleNode wrap(List names, List nodes) { - return new TupleNode(names, nodes); - } - - public static TupleNode copy(List names, List nodes) { - return new TupleNode(new ArrayList<>(names), new ArrayList<>(nodes)); - } - - public boolean isTuple() { - return true; - } - @Override public DataType getDataType() { return TupleType.wrap(names, nodes.stream().map(DataStructureNode::getDataType).toList()); @@ -69,6 +56,15 @@ public class TupleNode extends DataStructureNode { return names.get(index); } + @Override + public List getKeyValuePairs() { + var l = new ArrayList(size()); + for (int i = 0; i < size(); i++) { + l.add(new KeyValue(getNames().get(i), getNodes().get(i))); + } + return l; + } + public List getNames() { return Collections.unmodifiableList(names); } 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 new file mode 100644 index 000000000..eb5b52091 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/data/node/TupleNode.java @@ -0,0 +1,115 @@ +package io.xpipe.core.data.node; + +import io.xpipe.core.data.DataStructureNode; +import lombok.Value; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public abstract class TupleNode extends DataStructureNode { + + @Value + public static class KeyValue { + + String key; + DataStructureNode value; + } + + public static class Builder { + + private final List entries = new ArrayList<>(); + + public Builder add(String name, DataStructureNode node) { + Objects.requireNonNull(node); + entries.add(new KeyValue(name, node)); + return this; + } + + public Builder add(DataStructureNode node) { + Objects.requireNonNull(node); + entries.add(new KeyValue(null, node)); + return this; + } + + public TupleNode build() { + boolean hasKeys = entries.stream().anyMatch(kv -> kv.key != null); + return hasKeys ? TupleNode.wrap( + entries.stream().map(kv -> kv.key).toList(), + entries.stream().map(kv -> kv.value).toList()) : + TupleNode.wrap(entries.stream().map(kv -> kv.value).toList()); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static TupleNode wrap(List nodes) { + if (nodes == null) { + throw new IllegalArgumentException("Nodes must be not null"); + } + + return new NoKeyTupleNode(nodes); + } + + public static TupleNode copy(List nodes) { + return TupleNode.wrap(List.copyOf(nodes)); + } + + public static TupleNode wrap(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"); + } + if (names.size() != nodes.size()) { + throw new IllegalArgumentException("Names and nodes must have the same length"); + } + + return new SimpleTupleNode(names, nodes); + } + + public static TupleNode wrapRaw(List names, List nodes) { + if (names == null) { + throw new IllegalArgumentException("Names must be not null"); + } + if (nodes == null) { + throw new IllegalArgumentException("Nodes must be not null"); + } + return new SimpleTupleNode(names, nodes); + } + + public static TupleNode copy(List names, List nodes) { + return TupleNode.wrap(List.copyOf(names), List.copyOf(nodes)); + } + + public final boolean isTuple() { + return true; + } + + @Override + public String toString(int indent) { + var is = " ".repeat(indent); + var start = getClass().getSimpleName() + " {\n"; + var kvs = getKeyValuePairs().stream().map(kv -> { + if (kv.key == null) { + return is + " " + kv.value.toString(indent + 1) + "\n"; + } else { + return is + " " + kv.key + "=" + kv.value.toString(indent + 1) + "\n"; + } + }).collect(Collectors.joining()); + var end = is + "}"; + return start + kvs + end; + } + + public abstract String nameAt(int index); + + public abstract List getKeyValuePairs(); + + public abstract List getNames(); + + public abstract List getNodes(); +} diff --git a/core/src/main/java/io/xpipe/core/data/generic/ValueNode.java b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java similarity index 69% rename from core/src/main/java/io/xpipe/core/data/generic/ValueNode.java rename to core/src/main/java/io/xpipe/core/data/node/ValueNode.java index 7581829d0..bc336f499 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/ValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java @@ -1,9 +1,13 @@ -package io.xpipe.core.data.generic; +package io.xpipe.core.data.node; import io.xpipe.core.data.DataStructureNode; import io.xpipe.core.data.type.DataType; import io.xpipe.core.data.type.ValueType; +import lombok.EqualsAndHashCode; +import java.nio.charset.StandardCharsets; + +@EqualsAndHashCode(callSuper = false) public class ValueNode extends DataStructureNode { private final byte[] data; @@ -16,6 +20,10 @@ public class ValueNode extends DataStructureNode { return new ValueNode(data); } + public static ValueNode of(Object o) { + return new ValueNode(o.toString().getBytes(StandardCharsets.UTF_8)); + } + @Override public boolean isValue() { return true; @@ -36,6 +44,11 @@ public class ValueNode extends DataStructureNode { return "value node"; } + @Override + public String toString(int indent) { + return new String(data); + } + @Override public DataType getDataType() { return new ValueType(); diff --git a/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallbacks.java b/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallbacks.java index 4ecf24e87..2a46dd552 100644 --- a/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallbacks.java +++ b/core/src/main/java/io/xpipe/core/data/type/callback/DataTypeCallbacks.java @@ -1,6 +1,6 @@ package io.xpipe.core.data.type.callback; -import io.xpipe.core.data.generic.DataStructureNodePointer; +import io.xpipe.core.data.DataStructureNodePointer; import io.xpipe.core.data.type.ArrayType; import io.xpipe.core.data.type.TupleType; diff --git a/core/src/main/java/io/xpipe/core/data/type/callback/TableTypeCallback.java b/core/src/main/java/io/xpipe/core/data/type/callback/TableTypeCallback.java index dbc27bb58..8b12d7bb3 100644 --- a/core/src/main/java/io/xpipe/core/data/type/callback/TableTypeCallback.java +++ b/core/src/main/java/io/xpipe/core/data/type/callback/TableTypeCallback.java @@ -1,6 +1,6 @@ package io.xpipe.core.data.type.callback; -import io.xpipe.core.data.generic.DataStructureNodePointer; +import io.xpipe.core.data.DataStructureNodePointer; import io.xpipe.core.data.type.ArrayType; import io.xpipe.core.data.type.TupleType; diff --git a/core/src/main/java/io/xpipe/core/data/type/callback/ReusableTypedDataStructureNodeCallback.java b/core/src/main/java/io/xpipe/core/data/typed/ReusableTypedDataStructureNodeCallback.java similarity index 93% rename from core/src/main/java/io/xpipe/core/data/type/callback/ReusableTypedDataStructureNodeCallback.java rename to core/src/main/java/io/xpipe/core/data/typed/ReusableTypedDataStructureNodeCallback.java index 19099e04f..1c9ec880c 100644 --- a/core/src/main/java/io/xpipe/core/data/type/callback/ReusableTypedDataStructureNodeCallback.java +++ b/core/src/main/java/io/xpipe/core/data/typed/ReusableTypedDataStructureNodeCallback.java @@ -1,4 +1,4 @@ -package io.xpipe.core.data.type.callback; +package io.xpipe.core.data.typed; public class ReusableTypedDataStructureNodeCallback implements TypedDataStreamCallback { diff --git a/core/src/main/java/io/xpipe/core/data/type/callback/TypedDataStreamCallback.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java similarity index 89% rename from core/src/main/java/io/xpipe/core/data/type/callback/TypedDataStreamCallback.java rename to core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java index 8efbf8774..740cbef7f 100644 --- a/core/src/main/java/io/xpipe/core/data/type/callback/TypedDataStreamCallback.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamCallback.java @@ -1,4 +1,4 @@ -package io.xpipe.core.data.type.callback; +package io.xpipe.core.data.typed; public interface TypedDataStreamCallback { diff --git a/core/src/main/java/io/xpipe/core/data/type/TypedDataStreamReader.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamReader.java similarity index 90% rename from core/src/main/java/io/xpipe/core/data/type/TypedDataStreamReader.java rename to core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamReader.java index e606fdce0..58eada1f5 100644 --- a/core/src/main/java/io/xpipe/core/data/type/TypedDataStreamReader.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamReader.java @@ -1,6 +1,4 @@ -package io.xpipe.core.data.type; - -import io.xpipe.core.data.type.callback.TypedDataStreamCallback; +package io.xpipe.core.data.typed; import java.io.IOException; import java.io.InputStream; @@ -19,18 +17,14 @@ public class TypedDataStreamReader { } if (b != STRUCTURE_ID) { - throw new IOException("Unexpected value: " + b); + throw new IllegalStateException("Unexpected value: " + b); } return true; } public static void readStructures(InputStream in, TypedDataStreamCallback cb) throws IOException { - while (true) { - if (!hasNext(in)) { - break; - } - + while (hasNext(in)) { cb.onNodeBegin(); read(in, cb); cb.onNodeEnd(); diff --git a/core/src/main/java/io/xpipe/core/data/type/TypedDataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java similarity index 82% rename from core/src/main/java/io/xpipe/core/data/type/TypedDataStreamWriter.java rename to core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java index e7b639ef1..2de27ebfc 100644 --- a/core/src/main/java/io/xpipe/core/data/type/TypedDataStreamWriter.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java @@ -1,9 +1,9 @@ -package io.xpipe.core.data.type; +package io.xpipe.core.data.typed; import io.xpipe.core.data.DataStructureNode; -import io.xpipe.core.data.generic.ArrayNode; -import io.xpipe.core.data.generic.TupleNode; -import io.xpipe.core.data.generic.ValueNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.SimpleTupleNode; +import io.xpipe.core.data.node.ValueNode; import java.io.IOException; import java.io.OutputStream; @@ -22,7 +22,7 @@ public class TypedDataStreamWriter { private static void write(OutputStream out, DataStructureNode node) throws IOException { if (node.isTuple()) { - writeTuple(out, (TupleNode) node); + writeTuple(out, (SimpleTupleNode) node); } else if (node.isArray()) { writeArray(out, (ArrayNode) node); @@ -40,7 +40,7 @@ public class TypedDataStreamWriter { out.write(n.getRawData()); } - private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException { + private static void writeTuple(OutputStream out, SimpleTupleNode tuple) throws IOException { out.write(TUPLE_ID); out.write(tuple.size()); for (int i = 0; i < tuple.size(); i++) { diff --git a/core/src/main/java/io/xpipe/core/data/type/callback/TypedDataStructureNodeCallback.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java similarity index 59% rename from core/src/main/java/io/xpipe/core/data/type/callback/TypedDataStructureNodeCallback.java rename to core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java index 2061f7002..dfc076f5f 100644 --- a/core/src/main/java/io/xpipe/core/data/type/callback/TypedDataStructureNodeCallback.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStructureNodeReader.java @@ -1,28 +1,29 @@ -package io.xpipe.core.data.type.callback; +package io.xpipe.core.data.typed; import io.xpipe.core.data.DataStructureNode; -import io.xpipe.core.data.generic.ArrayNode; -import io.xpipe.core.data.generic.TupleNode; -import io.xpipe.core.data.generic.ValueNode; +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.TupleType; +import io.xpipe.core.data.type.callback.DataTypeCallback; import java.util.ArrayList; import java.util.List; import java.util.Stack; -import java.util.function.Consumer; -public class TypedDataStructureNodeCallback implements TypedDataStreamCallback { +public class TypedDataStructureNodeReader implements TypedDataStreamCallback { + private int currentDataTypeIndex; private final List flattened; - private int dataTypeIndex; private Stack> children; private Stack nodes; private DataStructureNode readNode; - private final Consumer consumer; + private boolean initialized; + private int arrayDepth; - public TypedDataStructureNodeCallback(DataType type, Consumer consumer) { - this.consumer = consumer; + public TypedDataStructureNodeReader(DataType type) { flattened = new ArrayList<>(); children = new Stack<>(); nodes = new Stack<>(); @@ -35,51 +36,43 @@ public class TypedDataStructureNodeCallback implements TypedDataStreamCallback { throw new IllegalStateException(); } - dataTypeIndex = 0; readNode = null; } + public DataStructureNode create() { + return readNode; + } + @Override public void onNodeEnd() { if (nodes.size() != 0 || children.size() != 0 || readNode == null) { throw new IllegalStateException(); } - consumer.accept(readNode); + initialized = false; } @Override public void onValue(byte[] data) { - children.peek().add(ValueNode.wrap(data)); - if (!flattened.get(dataTypeIndex).isArray()) { - dataTypeIndex++; + if (!initialized) { + readNode = ValueNode.wrap(data); + return; } - } - protected void newTuple() { - TupleType tupleType = (TupleType) flattened.get(dataTypeIndex); - var l = new ArrayList(tupleType.getSize()); - children.push(l); - var newNode = TupleNode.wrap(tupleType.getNames(), l); - nodes.push(newNode); - } - - protected void newArray() { - var l = new ArrayList(); - children.push(new ArrayList<>()); - var newNode = ArrayNode.wrap(l); - nodes.push(newNode); + children.peek().add(ValueNode.wrap(data)); + if (!flattened.get(currentDataTypeIndex).isArray()) { + currentDataTypeIndex++; + } } private void finishTuple() { children.pop(); - dataTypeIndex++; var popped = nodes.pop(); if (!popped.isTuple()) { throw new IllegalStateException(); } - TupleNode tuple = (TupleNode) popped; + SimpleTupleNode tuple = (SimpleTupleNode) popped; if (tuple.getNames().size() != tuple.getNodes().size()) { throw new IllegalStateException(""); } @@ -91,9 +84,17 @@ public class TypedDataStructureNodeCallback implements TypedDataStreamCallback { } } + private boolean isInArray() { + return arrayDepth >= 1; + } + private void finishArray() { + arrayDepth--; + if (!isInArray()) { + currentDataTypeIndex++; + } + children.pop(); - dataTypeIndex++; var popped = nodes.pop(); if (nodes.empty()) { readNode = popped; @@ -104,11 +105,26 @@ public class TypedDataStructureNodeCallback implements TypedDataStreamCallback { @Override public void onTupleBegin(int size) { - if (!flattened.get(dataTypeIndex).isTuple()) { + if (flattened.size() == currentDataTypeIndex) { + int a = 0; + } + + if (!isInArray() && !flattened.get(currentDataTypeIndex).isTuple()) { throw new IllegalStateException(); } - newTuple(); + TupleType tupleType = (TupleType) flattened.get(currentDataTypeIndex); + if (!initialized || !flattened.get(currentDataTypeIndex).isArray()) { + currentDataTypeIndex++; + } + if (!initialized) { + initialized = true; + } + + var l = new ArrayList(tupleType.getSize()); + children.push(l); + var newNode = TupleNode.wrapRaw(tupleType.getNames(), l); + nodes.push(newNode); } @Override @@ -118,11 +134,15 @@ public class TypedDataStructureNodeCallback implements TypedDataStreamCallback { @Override public void onArrayBegin(int size) { - if (!flattened.get(dataTypeIndex).isArray()) { + if (!flattened.get(currentDataTypeIndex).isArray()) { throw new IllegalStateException(); } - newArray(); + var l = new ArrayList(); + children.push(l); + var newNode = ArrayNode.wrap(l); + nodes.push(newNode); + arrayDepth++; } @Override diff --git a/core/src/main/java/io/xpipe/core/source/DataSource.java b/core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java similarity index 78% rename from core/src/main/java/io/xpipe/core/source/DataSource.java rename to core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java index b7373d39c..84f72de41 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSource.java +++ b/core/src/main/java/io/xpipe/core/source/DataSourceDescriptor.java @@ -4,7 +4,7 @@ import io.xpipe.core.store.DataStore; import java.util.Optional; -public interface DataSource { +public interface DataSourceDescriptor { default Optional determineDefaultName(DS store) { return Optional.empty(); diff --git a/core/src/main/java/io/xpipe/core/source/DataSourceId.java b/core/src/main/java/io/xpipe/core/source/DataSourceId.java index d30dd3c36..c327c57ce 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSourceId.java +++ b/core/src/main/java/io/xpipe/core/source/DataSourceId.java @@ -1,15 +1,40 @@ package io.xpipe.core.source; import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.EqualsAndHashCode; +import lombok.Getter; +/** + * Represents a reference to an XPipe data source. + * This reference consists out of a collection name and an entry name to allow for better organisation. + * + * To allow for a simple usage of data source ids, the collection and entry names are trimmed and + * converted to lower case names when creating them. + * The two names are separated by a colon and are therefore not allowed to contain colons themselves. + * + * @see #create(String, String) + * @see #fromString(String) + */ +@EqualsAndHashCode +@Getter public class DataSourceId { public static final char SEPARATOR = ':'; + /** + * Creates a new data source id from a collection name and an entry name. + * + * @param collectionName the collection name, which must be not null and not empty + * @param entryName the entry name, which must be not null and not empty + * @throws IllegalArgumentException if any name is not valid + */ public static DataSourceId create(String collectionName, String entryName) { if (collectionName == null) { throw new IllegalArgumentException("Collection name is null"); } + if (collectionName.trim().length() == 0) { + throw new IllegalArgumentException("Trimmed collection name is empty"); + } if (collectionName.contains("" + SEPARATOR)) { throw new IllegalArgumentException("Separator character " + SEPARATOR + " is not allowed in the collection name"); } @@ -17,6 +42,9 @@ public class DataSourceId { if (entryName == null) { throw new IllegalArgumentException("Collection name is null"); } + if (entryName.trim().length() == 0) { + throw new IllegalArgumentException("Trimmed entry name is empty"); + } if (entryName.contains("" + SEPARATOR)) { throw new IllegalArgumentException("Separator character " + SEPARATOR + " is not allowed in the entry name"); } @@ -33,6 +61,13 @@ public class DataSourceId { this.entryName = entryName; } + /** + * Creates a new data source id from a string representation. + * The string must contain exactly one colon and non-empty names. + * + * @param s the string representation, must be not null and fulfill certain requirements + * @throws IllegalArgumentException if the string is not valid + */ public static DataSourceId fromString(String s) { if (s == null) { throw new IllegalArgumentException("String is null"); @@ -43,30 +78,20 @@ public class DataSourceId { throw new IllegalArgumentException("Data source id must contain exactly one " + SEPARATOR); } - if (split[0].length() == 0) { + var cn = split[0].trim().toLowerCase(); + var en = split[1].trim().toLowerCase(); + if (cn.length() == 0) { throw new IllegalArgumentException("Collection name must not be empty"); } - if (split[1].length() == 0) { + if (en.length() == 0) { throw new IllegalArgumentException("Entry name must not be empty"); } - return new DataSourceId(split[0], split[1]); + return new DataSourceId(cn, en); } @Override public String toString() { - return (collectionName != null ? collectionName : "") + SEPARATOR + entryName; - } - - public String toReferenceValue() { - return toString().toLowerCase(); - } - - public String getCollectionName() { - return collectionName; - } - - public String getEntryName() { - return entryName; + return collectionName + SEPARATOR + entryName; } } diff --git a/core/src/main/java/io/xpipe/core/source/DataSourceType.java b/core/src/main/java/io/xpipe/core/source/DataSourceType.java index e10894aad..68d2139b5 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSourceType.java +++ b/core/src/main/java/io/xpipe/core/source/DataSourceType.java @@ -8,5 +8,8 @@ public enum DataSourceType { TABLE, @JsonProperty("structure") - STRUCTURE + STRUCTURE, + + @JsonProperty("raw") + RAW } diff --git a/core/src/main/java/io/xpipe/core/source/DataStructureConnection.java b/core/src/main/java/io/xpipe/core/source/DataStructureConnection.java deleted file mode 100644 index b60a308fc..000000000 --- a/core/src/main/java/io/xpipe/core/source/DataStructureConnection.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.xpipe.core.source; - -public interface DataStructureConnection extends DataSourceConnection { - -} diff --git a/core/src/main/java/io/xpipe/core/source/DataTableSource.java b/core/src/main/java/io/xpipe/core/source/DataTableSource.java deleted file mode 100644 index b4348a6b1..000000000 --- a/core/src/main/java/io/xpipe/core/source/DataTableSource.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.xpipe.core.source; - -import io.xpipe.core.store.DataStore; - -public abstract class DataTableSource implements DataSource { - - public abstract DataTableWriteConnection openWriteConnection(DS store); - - public abstract DataTableConnection openConnection(DS store); - - @Override - public DataSourceType getType() { - return DataSourceType.TABLE; - } -} diff --git a/core/src/main/java/io/xpipe/core/source/DataTableWriteConnection.java b/core/src/main/java/io/xpipe/core/source/DataTableWriteConnection.java deleted file mode 100644 index 4256043f9..000000000 --- a/core/src/main/java/io/xpipe/core/source/DataTableWriteConnection.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.xpipe.core.source; - -import io.xpipe.core.data.DataStructureNodeAcceptor; -import io.xpipe.core.data.generic.ArrayNode; -import io.xpipe.core.data.generic.TupleNode; - -public interface DataTableWriteConnection extends DataSourceConnection { - - DataStructureNodeAcceptor writeLinesAcceptor(); - - void writeLines(ArrayNode lines) throws Exception; -} diff --git a/core/src/main/java/io/xpipe/core/source/StructureDataReadConnection.java b/core/src/main/java/io/xpipe/core/source/StructureDataReadConnection.java new file mode 100644 index 000000000..11292105a --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/StructureDataReadConnection.java @@ -0,0 +1,5 @@ +package io.xpipe.core.source; + +public interface StructureDataReadConnection extends DataSourceConnection { + +} diff --git a/core/src/main/java/io/xpipe/core/source/DataStructureSource.java b/core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java similarity index 70% rename from core/src/main/java/io/xpipe/core/source/DataStructureSource.java rename to core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java index 1b3cc67a6..ea4d3cb8b 100644 --- a/core/src/main/java/io/xpipe/core/source/DataStructureSource.java +++ b/core/src/main/java/io/xpipe/core/source/StructureDataSourceDescriptor.java @@ -1,6 +1,6 @@ package io.xpipe.core.source; -public abstract class DataStructureSource implements DataSource { +public abstract class StructureDataSourceDescriptor implements DataSourceDescriptor { public abstract DataSourceConnection openConnection() throws Exception; diff --git a/core/src/main/java/io/xpipe/core/source/DataTableConnection.java b/core/src/main/java/io/xpipe/core/source/TableDataReadConnection.java similarity index 60% rename from core/src/main/java/io/xpipe/core/source/DataTableConnection.java rename to core/src/main/java/io/xpipe/core/source/TableDataReadConnection.java index fe6be84ed..cf03518eb 100644 --- a/core/src/main/java/io/xpipe/core/source/DataTableConnection.java +++ b/core/src/main/java/io/xpipe/core/source/TableDataReadConnection.java @@ -2,19 +2,19 @@ package io.xpipe.core.source; import io.xpipe.core.data.DataStructureNodeAcceptor; -import io.xpipe.core.data.generic.ArrayNode; -import io.xpipe.core.data.generic.TupleNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.SimpleTupleNode; import io.xpipe.core.data.type.TupleType; import java.io.OutputStream; -public interface DataTableConnection extends DataSourceConnection { +public interface TableDataReadConnection extends DataSourceConnection { TupleType determineDataType() throws Exception; int determineRowCount() throws Exception; - void withLines(DataStructureNodeAcceptor lineAcceptor) throws Exception; + void withLines(DataStructureNodeAcceptor lineAcceptor) throws Exception; ArrayNode readLines(int maxLines) throws Exception; diff --git a/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java b/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java new file mode 100644 index 000000000..41f595c33 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/TableDataSourceDescriptor.java @@ -0,0 +1,15 @@ +package io.xpipe.core.source; + +import io.xpipe.core.store.DataStore; + +public abstract class TableDataSourceDescriptor implements DataSourceDescriptor { + + public abstract TableDataWriteConnection openWriteConnection(DS store); + + public abstract TableDataReadConnection openConnection(DS store); + + @Override + public DataSourceType getType() { + return DataSourceType.TABLE; + } +} diff --git a/core/src/main/java/io/xpipe/core/source/TableDataWriteConnection.java b/core/src/main/java/io/xpipe/core/source/TableDataWriteConnection.java new file mode 100644 index 000000000..907e3768f --- /dev/null +++ b/core/src/main/java/io/xpipe/core/source/TableDataWriteConnection.java @@ -0,0 +1,12 @@ +package io.xpipe.core.source; + +import io.xpipe.core.data.DataStructureNodeAcceptor; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.SimpleTupleNode; + +public interface TableDataWriteConnection extends DataSourceConnection { + + DataStructureNodeAcceptor writeLinesAcceptor(); + + void writeLines(ArrayNode lines) throws Exception; +} diff --git a/core/src/main/java/io/xpipe/core/store/FileDataInput.java b/core/src/main/java/io/xpipe/core/store/FileDataStore.java similarity index 56% rename from core/src/main/java/io/xpipe/core/store/FileDataInput.java rename to core/src/main/java/io/xpipe/core/store/FileDataStore.java index 60628c1a3..1877793dd 100644 --- a/core/src/main/java/io/xpipe/core/store/FileDataInput.java +++ b/core/src/main/java/io/xpipe/core/store/FileDataStore.java @@ -2,7 +2,7 @@ package io.xpipe.core.store; import com.fasterxml.jackson.annotation.JsonIgnore; -public abstract class FileDataInput implements StreamDataStore { +public abstract class FileDataStore implements StreamDataStore { public abstract String getName(); @@ -10,8 +10,8 @@ public abstract class FileDataInput implements StreamDataStore { public abstract boolean isLocal(); @JsonIgnore - public abstract LocalFileDataInput getLocal(); + public abstract LocalFileDataStore getLocal(); @JsonIgnore - public abstract RemoteFileDataInput getRemote(); + public abstract RemoteFileDataStore getRemote(); } diff --git a/core/src/main/java/io/xpipe/core/store/LocalFileDataInput.java b/core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java similarity index 88% rename from core/src/main/java/io/xpipe/core/store/LocalFileDataInput.java rename to core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java index 1ea588d57..85150b43a 100644 --- a/core/src/main/java/io/xpipe/core/store/LocalFileDataInput.java +++ b/core/src/main/java/io/xpipe/core/store/LocalFileDataStore.java @@ -12,12 +12,12 @@ import java.time.Instant; import java.util.Optional; @JsonTypeName("local") -public class LocalFileDataInput extends FileDataInput { +public class LocalFileDataStore extends FileDataStore { private final Path file; @JsonCreator - public LocalFileDataInput(Path file) { + public LocalFileDataStore(Path file) { this.file = file; } @@ -48,12 +48,12 @@ public class LocalFileDataInput extends FileDataInput { } @Override - public LocalFileDataInput getLocal() { + public LocalFileDataStore getLocal() { return this; } @Override - public RemoteFileDataInput getRemote() { + public RemoteFileDataStore getRemote() { throw new UnsupportedOperationException(); } diff --git a/core/src/main/java/io/xpipe/core/store/RemoteFileDataInput.java b/core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java similarity index 84% rename from core/src/main/java/io/xpipe/core/store/RemoteFileDataInput.java rename to core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java index 362581018..2b57e82e2 100644 --- a/core/src/main/java/io/xpipe/core/store/RemoteFileDataInput.java +++ b/core/src/main/java/io/xpipe/core/store/RemoteFileDataStore.java @@ -5,7 +5,7 @@ import java.io.OutputStream; import java.time.Instant; import java.util.Optional; -public class RemoteFileDataInput extends FileDataInput { +public class RemoteFileDataStore extends FileDataStore { @Override public Optional determineDefaultName() { @@ -28,12 +28,12 @@ public class RemoteFileDataInput extends FileDataInput { } @Override - public LocalFileDataInput getLocal() { + public LocalFileDataStore getLocal() { return null; } @Override - public RemoteFileDataInput getRemote() { + public RemoteFileDataStore getRemote() { return null; } diff --git a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java index 6a156f607..50860441b 100644 --- a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java +++ b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import io.xpipe.core.data.type.ArrayType; import io.xpipe.core.data.type.TupleType; import io.xpipe.core.data.type.ValueType; -import io.xpipe.core.store.LocalFileDataInput; +import io.xpipe.core.store.LocalFileDataStore; import java.io.IOException; import java.nio.charset.Charset; @@ -57,7 +57,7 @@ public class CoreJacksonModule extends SimpleModule { @Override public void setupModule(SetupContext context) { context.registerSubtypes( - new NamedType(LocalFileDataInput.class), + new NamedType(LocalFileDataStore.class), new NamedType(ValueType.class), new NamedType(TupleType.class), new NamedType(ArrayType.class) diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 29462f6bb..647fd4f0e 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -5,6 +5,8 @@ module io.xpipe.core { requires transitive com.fasterxml.jackson.databind; requires transitive com.fasterxml.jackson.module.paramnames; + requires static lombok; + exports io.xpipe.core.store; exports io.xpipe.core.source; exports io.xpipe.core.data.generic; @@ -19,6 +21,10 @@ module io.xpipe.core { exports io.xpipe.core.data; opens io.xpipe.core.data; exports io.xpipe.core.util; + exports io.xpipe.core.data.node; + opens io.xpipe.core.data.node; + exports io.xpipe.core.data.typed; + opens io.xpipe.core.data.typed; uses com.fasterxml.jackson.databind.Module; provides com.fasterxml.jackson.databind.Module with CoreJacksonModule; diff --git a/core/src/test/java/io/xpipe/core/test/DataSourceIdTest.java b/core/src/test/java/io/xpipe/core/test/DataSourceIdTest.java new file mode 100644 index 000000000..47155950e --- /dev/null +++ b/core/src/test/java/io/xpipe/core/test/DataSourceIdTest.java @@ -0,0 +1,62 @@ +package io.xpipe.core.test; + +import io.xpipe.core.source.DataSourceId; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class DataSourceIdTest { + + @Test + public void testCreateInvalidParameters() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create(null, "abc"); + }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create("a:bc", "abc"); + }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create(" \t", "abc"); + }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create("", "abc"); + }); + + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create("abc", null); + }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create("abc", "a:bc"); + }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create("abc", " \t"); + }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.create("abc", ""); + }); + } + + @Test + public void testFromStringNullParameters() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.fromString(null); + }); + } + + @ParameterizedTest + @ValueSource(strings = {"abc", "abc:", "ab::c", ":abc", " :ab", "::::", "", " "}) + public void testFromStringInvalidParameters(String arg) { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + DataSourceId.fromString(arg); + }); + } + + @Test + public void testFromStringValidParameters() { + Assertions.assertEquals(DataSourceId.fromString("ab:c"), DataSourceId.fromString(" ab: c ")); + Assertions.assertEquals(DataSourceId.fromString("ab:c"), DataSourceId.fromString(" AB: C ")); + Assertions.assertEquals(DataSourceId.fromString("ab:c"), DataSourceId.fromString("ab:c ")); + } +} diff --git a/core/src/test/java/io/xpipe/core/test/DataStructureTest.java b/core/src/test/java/io/xpipe/core/test/DataStructureTest.java new file mode 100644 index 000000000..43bec32ad --- /dev/null +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTest.java @@ -0,0 +1,56 @@ +package io.xpipe.core.test; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.TupleNode; +import io.xpipe.core.data.node.ValueNode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class DataStructureTest { + + public static DataStructureNode createTestData() { + var val = ValueNode.wrap("value".getBytes(StandardCharsets.UTF_8)); + var flatArray = ArrayNode.wrap(List.of(ValueNode.of(1), ValueNode.of(2))); + var flatTuple = TupleNode.builder().add("key1", val).build(); + var nestedArray = ArrayNode.wrap(List.of(flatArray, flatTuple)); + return TupleNode.builder() + .add("key1", val) + .add("key2", flatArray) + .add("key3", flatTuple) + .add("key4", nestedArray) + .build(); + } + + @Test + public void testBasicOperations() { + var obj = createTestData(); + Assertions.assertEquals(obj.size(), 4); + Assertions.assertTrue(obj.isTuple()); + + var objCopy = createTestData(); + Assertions.assertEquals(obj, objCopy); + + var key1 = obj.forKey("key1").asString(); + Assertions.assertEquals(key1, "value"); + + var key2 = obj.forKey("key2"); + Assertions.assertTrue(key2.isArray()); + Assertions.assertEquals(key2.at(0), ValueNode.of(1)); + Assertions.assertEquals(key2.at(0).asString(), "1"); + Assertions.assertEquals(key2.at(0).asInt(), 1); + + var key3 = obj.forKey("key3"); + Assertions.assertTrue(key3.isTuple()); + Assertions.assertEquals(key3.forKey("key1"), ValueNode.of("value")); + Assertions.assertEquals(key3.forKey("key1").asString(), "value"); + + var key4 = obj.forKey("key4"); + Assertions.assertTrue(key4.isArray()); + Assertions.assertEquals(key4.at(0), ArrayNode.of(ValueNode.of(1), ValueNode.of(2))); + Assertions.assertEquals(key4.at(0).at(0).asInt(), 1); + } +} diff --git a/core/src/test/java/io/xpipe/core/test/DataStructureTests.java b/core/src/test/java/io/xpipe/core/test/DataStructureTests.java new file mode 100644 index 000000000..8c17a82ac --- /dev/null +++ b/core/src/test/java/io/xpipe/core/test/DataStructureTests.java @@ -0,0 +1,25 @@ +package io.xpipe.core.test; + +import io.xpipe.core.data.DataStructureNode; +import io.xpipe.core.data.node.ArrayNode; +import io.xpipe.core.data.node.TupleNode; +import io.xpipe.core.data.node.ValueNode; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class DataStructureTests { + + public static DataStructureNode createTestData() { + var val = ValueNode.wrap("value".getBytes(StandardCharsets.UTF_8)); + var flatArray = ArrayNode.wrap(List.of(ValueNode.of(1), ValueNode.of(2))); + var flatTuple = TupleNode.builder().add("key1", val).build(); + var nestedArray = ArrayNode.wrap(List.of(flatArray, flatTuple)); + return TupleNode.builder() + .add("key1", val) + .add("key2", flatArray) + .add("key3", flatTuple) + .add("key4", nestedArray) + .build(); + } +} diff --git a/core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java b/core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java new file mode 100644 index 000000000..10b29b438 --- /dev/null +++ b/core/src/test/java/io/xpipe/core/test/GenericDataStructureIoTest.java @@ -0,0 +1,35 @@ +package io.xpipe.core.test; + +import io.xpipe.core.data.generic.GenericDataStreamReader; +import io.xpipe.core.data.generic.GenericDataStreamWriter; +import io.xpipe.core.data.generic.GenericDataStructureReader; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HexFormat; + +import static io.xpipe.core.test.DataStructureTests.createTestData; + +public class GenericDataStructureIoTest { + + @Test + public void testBasicIo() throws IOException { + var obj = createTestData(); + var dataOut = new ByteArrayOutputStream(); + GenericDataStreamWriter.write(dataOut, obj); + var data = dataOut.toByteArray(); + + var format = HexFormat.of().withPrefix("0x").withDelimiter(" "); + System.out.println(format.formatHex(data)); + + var reader = new GenericDataStructureReader(); + GenericDataStreamReader.read(new ByteArrayInputStream(data), reader); + var node = reader.create(); + + Assertions.assertEquals(obj, node); + System.out.println(node); + } +} diff --git a/core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java b/core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java new file mode 100644 index 000000000..c3d44dc4b --- /dev/null +++ b/core/src/test/java/io/xpipe/core/test/TypedDataStructureIoTest.java @@ -0,0 +1,36 @@ +package io.xpipe.core.test; + +import io.xpipe.core.data.typed.TypedDataStreamReader; +import io.xpipe.core.data.typed.TypedDataStreamWriter; +import io.xpipe.core.data.typed.TypedDataStructureNodeReader; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HexFormat; + +import static io.xpipe.core.test.DataStructureTests.createTestData; + +public class TypedDataStructureIoTest { + + @Test + public void testBasicIo() throws IOException { + var obj = createTestData(); + var type = obj.getDataType(); + var dataOut = new ByteArrayOutputStream(); + TypedDataStreamWriter.writeStructure(dataOut, obj); + var data = dataOut.toByteArray(); + + var format = HexFormat.of().withPrefix("0x").withDelimiter(" "); + System.out.println(format.formatHex(data)); + + var reader = new TypedDataStructureNodeReader(type); + TypedDataStreamReader.readStructure(new ByteArrayInputStream(data), reader); + var node = reader.create(); + + Assertions.assertEquals(obj, node); + System.out.println(node); + } +} diff --git a/core/src/test/java/module-info.java b/core/src/test/java/module-info.java new file mode 100644 index 000000000..034b7d4e6 --- /dev/null +++ b/core/src/test/java/module-info.java @@ -0,0 +1,7 @@ +module io.xpipe.core.test { + exports io.xpipe.core.test; + + requires org.junit.jupiter.api; + requires org.junit.jupiter.params; + requires io.xpipe.core; +} \ No newline at end of file diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceGuiProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceGuiProvider.java index 464280877..ff26f9cb6 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceGuiProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceGuiProvider.java @@ -1,6 +1,6 @@ package io.xpipe.extension; -import io.xpipe.core.source.DataSource; +import io.xpipe.core.source.DataSourceDescriptor; import io.xpipe.core.store.DataStore; import javafx.beans.property.Property; import javafx.scene.image.Image; @@ -18,9 +18,9 @@ public interface DataSourceGuiProvider { boolean supportsFile(Path file); - Region createConfigOptions(DataStore input, Property> source); + Region createConfigOptions(DataStore input, Property> source); - DataSource createDefaultDataSource(DataStore input); + DataSourceDescriptor createDefaultDataSource(DataStore input); String getDisplayName(); @@ -30,7 +30,7 @@ public interface DataSourceGuiProvider { Map, String> getFileExtensions(); - String getDataSourceDescription(DataSource source); + String getDataSourceDescription(DataSourceDescriptor source); - Class> getType(); + Class> getType(); } diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java index 6ec41999e..0d85c8626 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java @@ -1,10 +1,10 @@ package io.xpipe.extension; -import io.xpipe.core.source.DataSource; +import io.xpipe.core.source.DataSourceDescriptor; public interface DataSourceProvider { String getId(); - Class> getType(); + Class> getType(); } diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java index 9546bc58f..97b600e10 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java @@ -1,7 +1,5 @@ package io.xpipe.extension; -import io.xpipe.core.source.DataSource; - import java.util.Optional; import java.util.ServiceLoader; import java.util.Set;