mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-06-24 00:53:02 +12:00
Initial commit
This commit is contained in:
commit
63cdfb40c5
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
* text=auto
|
||||||
|
*.png binary
|
||||||
|
*.xcf binary
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
.idea
|
||||||
|
dev.properties
|
||||||
|
extensions.txt
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "deps"]
|
||||||
|
path = deps
|
||||||
|
url = https://github.com/xpipe-io/xpipe_java_deps
|
44
api/build.gradle
Normal file
44
api/build.gradle
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id "org.moditect.gradleplugin" version "1.0.0-rc3"
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.withType(JavaPlugin).configureEach {
|
||||||
|
java {
|
||||||
|
modularity.inferModulePath = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
//workingDir = project(":app").projectDir
|
||||||
|
|
||||||
|
systemProperty 'io.xpipe.beacon.startInProcess', 'true'
|
||||||
|
systemProperty "io.xpipe.daemon.mode", 'base'
|
||||||
|
systemProperty "io.xpipe.storage.dir", "$projectDir/test_env"
|
||||||
|
systemProperty "io.xpipe.beacon.port", "21722"
|
||||||
|
}
|
28
api/src/main/java/io/xpipe/api/DataTable.java
Normal file
28
api/src/main/java/io/xpipe/api/DataTable.java
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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.type.DataType;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
|
public interface DataTable extends Iterable<TupleNode> {
|
||||||
|
|
||||||
|
static DataTable get(String s) {
|
||||||
|
return DataTableImpl.get(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSourceId getId();
|
||||||
|
|
||||||
|
int getRowCount();
|
||||||
|
|
||||||
|
OptionalInt getRowCountIfPresent();
|
||||||
|
|
||||||
|
DataType getDataType();
|
||||||
|
|
||||||
|
ArrayNode readAll();
|
||||||
|
|
||||||
|
ArrayNode read(int maxRows);
|
||||||
|
}
|
21
api/src/main/java/io/xpipe/api/IntConverter.java
Normal file
21
api/src/main/java/io/xpipe/api/IntConverter.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package io.xpipe.api;
|
||||||
|
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
|
public class IntConverter {
|
||||||
|
|
||||||
|
private IntConsumer consumer;
|
||||||
|
|
||||||
|
public IntConverter(IntConsumer consumer) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onValue(byte[] value) {
|
||||||
|
if (value.length > 4) {
|
||||||
|
throw new IllegalArgumentException("Unable to fit " + value.length + " bytes into an integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
int v = value[0] << 24 | (value[1] & 0xFF) << 16 | (value[2] & 0xFF) << 8 | (value[3] & 0xFF);
|
||||||
|
consumer.accept(v);
|
||||||
|
}
|
||||||
|
}
|
39
api/src/main/java/io/xpipe/api/XPipeApiConnector.java
Normal file
39
api/src/main/java/io/xpipe/api/XPipeApiConnector.java
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package io.xpipe.api;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.*;
|
||||||
|
import io.xpipe.beacon.socket.SocketClient;
|
||||||
|
|
||||||
|
public abstract class XPipeApiConnector extends XPipeConnector {
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
try {
|
||||||
|
var socket = constructSocket();
|
||||||
|
handle(socket);
|
||||||
|
} catch (ConnectorException ce) {
|
||||||
|
throw new XPipeException("Connection error: " + ce.getMessage());
|
||||||
|
} catch (ClientException ce) {
|
||||||
|
throw new XPipeException("Client error: " + ce.getMessage());
|
||||||
|
} catch (ServerException se) {
|
||||||
|
throw new XPipeException("Server error: " + se.getMessage());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new XPipeException("Unexpected error", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void handle(SocketClient sc) throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void waitForStartup() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public static interface Handler {
|
||||||
|
|
||||||
|
void handle(SocketClient sc) throws ClientException, ServerException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.api;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.DataStructureNode;
|
||||||
|
|
||||||
|
public interface XPipeDataStructureSource {
|
||||||
|
|
||||||
|
DataStructureNode read();
|
||||||
|
}
|
12
api/src/main/java/io/xpipe/api/XPipeDataTableBuilder.java
Normal file
12
api/src/main/java/io/xpipe/api/XPipeDataTableBuilder.java
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package io.xpipe.api;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
|
||||||
|
public abstract class XPipeDataTableBuilder {
|
||||||
|
|
||||||
|
private DataSourceId id;
|
||||||
|
|
||||||
|
public abstract void write();
|
||||||
|
|
||||||
|
public abstract void commit();
|
||||||
|
}
|
23
api/src/main/java/io/xpipe/api/XPipeException.java
Normal file
23
api/src/main/java/io/xpipe/api/XPipeException.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
164
api/src/main/java/io/xpipe/api/impl/DataTableImpl.java
Normal file
164
api/src/main/java/io/xpipe/api/impl/DataTableImpl.java
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package io.xpipe.api.impl;
|
||||||
|
|
||||||
|
import io.xpipe.api.DataTable;
|
||||||
|
import io.xpipe.api.XPipeApiConnector;
|
||||||
|
import io.xpipe.beacon.ClientException;
|
||||||
|
import io.xpipe.beacon.ConnectorException;
|
||||||
|
import io.xpipe.beacon.ServerException;
|
||||||
|
import io.xpipe.beacon.socket.SocketClient;
|
||||||
|
import io.xpipe.beacon.message.impl.ReadTableDataExchange;
|
||||||
|
import io.xpipe.beacon.message.impl.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.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.source.DataSourceId;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
public class DataTableImpl implements DataTable {
|
||||||
|
|
||||||
|
public static DataTable get(String s) {
|
||||||
|
final DataTable[] table = {null};
|
||||||
|
|
||||||
|
var ds = DataSourceId.fromString(s);
|
||||||
|
new XPipeApiConnector() {
|
||||||
|
@Override
|
||||||
|
protected void handle(SocketClient sc) throws ClientException, ServerException, ConnectorException {
|
||||||
|
var req = new ReadTableInfoExchange.Request(ds);
|
||||||
|
ReadTableInfoExchange.Response res = performSimpleExchange(sc, req);
|
||||||
|
table[0] = new DataTableImpl(res.sourceId(), res.rowCount(), res.dataType());
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
return table[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DataSourceId id;
|
||||||
|
private final int size;
|
||||||
|
private final DataType dataType;
|
||||||
|
|
||||||
|
public DataTableImpl(DataSourceId id, int size, DataType dataType) {
|
||||||
|
this.id = id;
|
||||||
|
this.size = size;
|
||||||
|
this.dataType = dataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<TupleNode> stream() {
|
||||||
|
return StreamSupport.stream(
|
||||||
|
Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceId getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
if (size == -1) {
|
||||||
|
throw new UnsupportedOperationException("Row count is unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt getRowCountIfPresent() {
|
||||||
|
return size != -1 ? OptionalInt.of(size) : OptionalInt.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataType getDataType() {
|
||||||
|
return dataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayNode readAll() {
|
||||||
|
return read(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayNode read(int maxRows) {
|
||||||
|
int maxToRead = size == -1 ? maxRows : Math.min(size, maxRows);
|
||||||
|
|
||||||
|
List<DataStructureNode> nodes = new ArrayList<>();
|
||||||
|
new XPipeApiConnector() {
|
||||||
|
@Override
|
||||||
|
protected void handle(SocketClient 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));
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
return ArrayNode.wrap(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<TupleNode> iterator() {
|
||||||
|
return new Iterator<TupleNode>() {
|
||||||
|
|
||||||
|
private InputStream input;
|
||||||
|
private int read;
|
||||||
|
private final int toRead = size;
|
||||||
|
private TypedDataStreamCallback callback;
|
||||||
|
private TupleNode current;
|
||||||
|
|
||||||
|
{
|
||||||
|
new XPipeApiConnector() {
|
||||||
|
@Override
|
||||||
|
protected void handle(SocketClient sc) throws ClientException, ServerException, ConnectorException {
|
||||||
|
var req = new ReadTableDataExchange.Request(id, Integer.MAX_VALUE);
|
||||||
|
performExchange(sc, req, (ReadTableDataExchange.Response res, InputStream in) -> {
|
||||||
|
input = in;
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
callback = new TypedDataStructureNodeCallback(dataType, dsn -> {
|
||||||
|
current = (TupleNode) dsn;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasKnownSize() {
|
||||||
|
return size != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (hasKnownSize() && read == toRead) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasKnownSize() && read < toRead) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return TypedDataStreamReader.hasNext(input);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TupleNode next() {
|
||||||
|
try {
|
||||||
|
TypedDataStreamReader.readStructure(input, callback);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
read++;
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
7
api/src/main/java/module-info.java
Normal file
7
api/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module io.xpipe.api {
|
||||||
|
requires io.xpipe.core;
|
||||||
|
requires io.xpipe.beacon;
|
||||||
|
requires org.apache.commons.lang;
|
||||||
|
|
||||||
|
exports io.xpipe.api;
|
||||||
|
}
|
16
api/src/test/java/io/xpipe/api/test/DataTableTest.java
Normal file
16
api/src/test/java/io/xpipe/api/test/DataTableTest.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package io.xpipe.api.test;
|
||||||
|
|
||||||
|
import io.xpipe.api.DataTable;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
@ExtendWith({XPipeConfig.class})
|
||||||
|
public class DataTableTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGet() {
|
||||||
|
var table = DataTable.get("new folder:username");
|
||||||
|
var r = table.read(2);
|
||||||
|
var a = 0;
|
||||||
|
}
|
||||||
|
}
|
28
api/src/test/java/io/xpipe/api/test/XPipeConfig.java
Normal file
28
api/src/test/java/io/xpipe/api/test/XPipeConfig.java
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package io.xpipe.api.test;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.XPipeDaemon;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
|
||||||
|
|
||||||
|
public class XPipeConfig implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
|
||||||
|
|
||||||
|
private static boolean started = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeAll(ExtensionContext context) throws Exception {
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
// 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);
|
||||||
|
XPipeDaemon.startDaemon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// Your "after all tests" logic goes here
|
||||||
|
}
|
||||||
|
}
|
8
api/src/test/java/module-info.java
Normal file
8
api/src/test/java/module-info.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module io.xpipe.api.test {
|
||||||
|
exports io.xpipe.api.test;
|
||||||
|
|
||||||
|
requires io.xpipe.api;
|
||||||
|
requires io.xpipe.beacon;
|
||||||
|
requires io.xpipe.app;
|
||||||
|
requires org.junit.jupiter.api;
|
||||||
|
}
|
4
api/start_test_daemon.bat
Normal file
4
api/start_test_daemon.bat
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
cd ..\app\
|
||||||
|
SET "dir=%~dp0test_env"
|
||||||
|
CALL ..\gradlew.bat run -Dio.xpipe.storage.dir=%dir% -Dio.xpipe.beacon.port=21722 -Dio.xpipe.daemon.mode=gui
|
||||||
|
pause
|
27
beacon/build.gradle
Normal file
27
beacon/build.gradle
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id "org.moditect.gradleplugin" version "1.0.0-rc3"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
modularity.inferModulePath = true
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/deps/slf4j.gradle"
|
||||||
|
apply from: "$rootDir/deps/websocket.gradle"
|
||||||
|
apply from: "$rootDir/deps/jackson.gradle"
|
||||||
|
apply from: "$rootDir/deps/commons.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':core')
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
public class ClientException extends Exception {
|
||||||
|
|
||||||
|
public ClientException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
23
beacon/src/main/java/io/xpipe/beacon/ConnectorException.java
Normal file
23
beacon/src/main/java/io/xpipe/beacon/ConnectorException.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
public class ConnectorException extends Exception {
|
||||||
|
|
||||||
|
public ConnectorException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectorException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectorException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectorException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
public class ServerException extends Exception {
|
||||||
|
|
||||||
|
public ServerException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
59
beacon/src/main/java/io/xpipe/beacon/XPipeConnector.java
Normal file
59
beacon/src/main/java/io/xpipe/beacon/XPipeConnector.java
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.beacon.socket.SocketClient;
|
||||||
|
import org.apache.commons.lang3.function.FailableBiConsumer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public abstract class XPipeConnector {
|
||||||
|
|
||||||
|
protected abstract void waitForStartup();
|
||||||
|
|
||||||
|
protected SocketClient constructSocket() throws ConnectorException {
|
||||||
|
if (!XPipeDaemon.isDaemonRunning()) {
|
||||||
|
try {
|
||||||
|
XPipeDaemon.startDaemon();
|
||||||
|
waitForStartup();
|
||||||
|
if (!XPipeDaemon.isDaemonRunning()) {
|
||||||
|
throw new ConnectorException("Unable to start xpipe daemon");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ConnectorException("Unable to start xpipe daemon: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new SocketClient();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ConnectorException("Unable to connect to running xpipe daemon: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <REQ extends RequestMessage, RES extends ResponseMessage> void performExchange(
|
||||||
|
SocketClient socket,
|
||||||
|
REQ req,
|
||||||
|
FailableBiConsumer<RES, InputStream, IOException> responseConsumer,
|
||||||
|
boolean keepOpen) throws ServerException, ConnectorException, ClientException {
|
||||||
|
performExchange(socket, req, null, responseConsumer, keepOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <REQ extends RequestMessage, RES extends ResponseMessage> void performExchange(
|
||||||
|
SocketClient socket,
|
||||||
|
REQ req,
|
||||||
|
Consumer<OutputStream> output,
|
||||||
|
FailableBiConsumer<RES, InputStream, IOException> responseConsumer,
|
||||||
|
boolean keepOpen) throws ServerException, ConnectorException, ClientException {
|
||||||
|
socket.exchange(req, output, responseConsumer, keepOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <REQ extends RequestMessage, RES extends ResponseMessage> RES performSimpleExchange(
|
||||||
|
SocketClient socket,
|
||||||
|
REQ req) throws ServerException, ConnectorException, ClientException {
|
||||||
|
return socket.simpleExchange(req);
|
||||||
|
}
|
||||||
|
}
|
58
beacon/src/main/java/io/xpipe/beacon/XPipeDaemon.java
Normal file
58
beacon/src/main/java/io/xpipe/beacon/XPipeDaemon.java
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
import io.xpipe.app.Main;
|
||||||
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class XPipeDaemon {
|
||||||
|
|
||||||
|
private static final String IN_PROCESS_PROP = "io.xpipe.beacon.startInProcess";
|
||||||
|
|
||||||
|
public static Path getUserDir() {
|
||||||
|
return Path.of(System.getProperty("user.home"), ".xpipe");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPortAvailable(int port) {
|
||||||
|
try (var ss = new ServerSocket(port); var ds = new DatagramSocket(port)) {
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDaemonRunning() {
|
||||||
|
var port = SocketServer.determineUsedPort();
|
||||||
|
return !isPortAvailable(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startDaemon() throws Exception {
|
||||||
|
if (Optional.ofNullable(System.getProperty("io.xpipe.beacon.startInProcess"))
|
||||||
|
.map(Boolean::parseBoolean).orElse(false)) {
|
||||||
|
startInProcess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (System.getenv().containsKey(EXEC_PROPERTY)) {
|
||||||
|
// Runtime.getRuntime().exec(System.getenv(EXEC_PROPERTY));
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
var file = getUserDir().resolve("run");
|
||||||
|
if (Files.exists(file)) {
|
||||||
|
Runtime.getRuntime().exec(Files.readString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unable to find xpipe daemon installation");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startInProcess() {
|
||||||
|
ThreadHelper.create("XPipe daemon", false, () -> Main.main(new String[0])).start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package io.xpipe.beacon.message;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.ClientException;
|
||||||
|
|
||||||
|
public record ClientErrorMessage(String message) {
|
||||||
|
|
||||||
|
public ClientException throwException() {
|
||||||
|
return new ClientException(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package io.xpipe.beacon.message;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public interface MessageProvider<RQ extends RequestMessage, RP extends ResponseMessage> {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
Class<RQ> getRequestClass();
|
||||||
|
|
||||||
|
Class<RP> getResponseClass();
|
||||||
|
|
||||||
|
default void handleRequest(SocketServer server, RQ msg, InputStream body, Socket clientSocket) throws Exception {}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package io.xpipe.beacon.message;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class MessageProviders {
|
||||||
|
|
||||||
|
private static Set<MessageProvider> ALL;
|
||||||
|
|
||||||
|
private static void loadAll() {
|
||||||
|
if (ALL == null) {
|
||||||
|
ALL = ServiceLoader.load(MessageProvider.class).stream()
|
||||||
|
.map(ServiceLoader.Provider::get).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageProvider<RQ, RP>> byId(String name) {
|
||||||
|
loadAll();
|
||||||
|
var r = ALL.stream().filter(d -> d.getId().equals(name)).findAny();
|
||||||
|
return Optional.ofNullable(r.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageProvider<RQ, RP>> byRequest(RQ req) {
|
||||||
|
loadAll();
|
||||||
|
var r = ALL.stream().filter(d -> d.getRequestClass().equals(req.getClass())).findAny();
|
||||||
|
return Optional.ofNullable(r.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageProvider<RQ, RP>> byResponse(RP rep) {
|
||||||
|
loadAll();
|
||||||
|
var r = ALL.stream().filter(d -> d.getResponseClass().equals(rep.getClass())).findAny();
|
||||||
|
return Optional.ofNullable(r.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<MessageProvider> getAll() {
|
||||||
|
loadAll();
|
||||||
|
return ALL;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.xpipe.beacon.message;
|
||||||
|
|
||||||
|
public interface RequestMessage {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.xpipe.beacon.message;
|
||||||
|
|
||||||
|
public interface ResponseMessage {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.xpipe.beacon.message;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.ServerException;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record ServerErrorMessage(UUID requestId, Throwable error) {
|
||||||
|
|
||||||
|
public void throwError() throws ServerException {
|
||||||
|
throw new ServerException(error.getMessage(), error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ListCollectionsExchange implements MessageProvider<ListCollectionsExchange.Request, ListCollectionsExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "listCollections";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Request> getRequestClass() {
|
||||||
|
return Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Response> getResponseClass() {
|
||||||
|
return Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request() implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Entry(String name, int count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response(List<Entry> entries) implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.storage.DataSourceStorage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ListEntriesExchange implements MessageProvider<ListEntriesExchange.Request, ListEntriesExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "listEntries";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Request> getRequestClass() {
|
||||||
|
return Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Response> getResponseClass() {
|
||||||
|
return Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request(String collection) implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static record Entry(String name, String type, String description, String date, String size) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response(List<Entry> entries) implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.OperationMode;
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class ModeExchange implements MessageProvider<ModeExchange.Request, ModeExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "mode";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ModeExchange.Request> getRequestClass() {
|
||||||
|
return ModeExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ModeExchange.Response> getResponseClass() {
|
||||||
|
return ModeExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request(String modeId) implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response() implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.storage.DataSourceStorage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class ReadStructureExchange implements MessageProvider<ReadStructureExchange.Request, ReadStructureExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "readStructure";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Request> getRequestClass() {
|
||||||
|
return Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Response> getResponseClass() {
|
||||||
|
return Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request(DataSourceId id) implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response() implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.storage.DataSourceStorage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class ReadTableDataExchange implements MessageProvider<ReadTableDataExchange.Request, ReadTableDataExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "readTable";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadTableDataExchange.Request> getRequestClass() {
|
||||||
|
return ReadTableDataExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadTableDataExchange.Response> getResponseClass() {
|
||||||
|
return ReadTableDataExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request(DataSourceId sourceId, int maxLines) implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response() implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
import io.xpipe.core.data.type.DataType;
|
||||||
|
import io.xpipe.core.source.DataSourceId;
|
||||||
|
import io.xpipe.storage.DataSourceStorage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class ReadTableInfoExchange implements MessageProvider<ReadTableInfoExchange.Request, ReadTableInfoExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "readTableInfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadTableInfoExchange.Request> getRequestClass() {
|
||||||
|
return ReadTableInfoExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<ReadTableInfoExchange.Response> getResponseClass() {
|
||||||
|
return ReadTableInfoExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request(DataSourceId sourceId) implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response(DataSourceId sourceId, DataType dataType, int rowCount) implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.OperationMode;
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class StatusExchange implements MessageProvider<StatusExchange.Request, StatusExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "status";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Request> getRequestClass() {
|
||||||
|
return Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Response> getResponseClass() {
|
||||||
|
return Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request() implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Response(String mode) implements ResponseMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package io.xpipe.beacon.message.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppInstallation;
|
||||||
|
import io.xpipe.beacon.socket.SocketServer;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.RequestMessage;
|
||||||
|
import io.xpipe.beacon.message.ResponseMessage;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class VersionExchange implements MessageProvider<VersionExchange.Request, VersionExchange.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "version";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<VersionExchange.Request> getRequestClass() {
|
||||||
|
return VersionExchange.Request.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<VersionExchange.Response> getResponseClass() {
|
||||||
|
return VersionExchange.Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static record Request() implements RequestMessage {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
|
||||||
|
public final String version;
|
||||||
|
public final String jvmVersion;
|
||||||
|
|
||||||
|
public Response(String version, String jvmVersion) {
|
||||||
|
this.version = version;
|
||||||
|
this.jvmVersion = jvmVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
beacon/src/main/java/io/xpipe/beacon/socket/SocketClient.java
Normal file
200
beacon/src/main/java/io/xpipe/beacon/socket/SocketClient.java
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
package io.xpipe.beacon.socket;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
|
import io.xpipe.beacon.ClientException;
|
||||||
|
import io.xpipe.beacon.ConnectorException;
|
||||||
|
import io.xpipe.beacon.ServerException;
|
||||||
|
import io.xpipe.beacon.message.*;
|
||||||
|
import io.xpipe.core.util.JacksonHelper;
|
||||||
|
import org.apache.commons.lang3.function.FailableBiConsumer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static io.xpipe.beacon.socket.Sockets.BODY_SEPARATOR;
|
||||||
|
|
||||||
|
public class SocketClient {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
|
||||||
|
|
||||||
|
private final Socket socket;
|
||||||
|
private final InputStream in;
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
public SocketClient() throws IOException {
|
||||||
|
socket = new Socket(InetAddress.getLoopbackAddress(), SocketServer.determineUsedPort());
|
||||||
|
in = socket.getInputStream();
|
||||||
|
out = socket.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws ConnectorException {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't close socket", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <REQ extends RequestMessage, RES extends ResponseMessage> void exchange(
|
||||||
|
REQ req,
|
||||||
|
Consumer<OutputStream> output,
|
||||||
|
FailableBiConsumer<RES, InputStream, IOException> responseConsumer,
|
||||||
|
boolean keepOpen) throws ConnectorException, ClientException, ServerException {
|
||||||
|
try {
|
||||||
|
sendRequest(req);
|
||||||
|
if (output != null) {
|
||||||
|
out.write(BODY_SEPARATOR);
|
||||||
|
output.accept(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = this.<RES>receiveResponse();
|
||||||
|
var sep = in.readNBytes(BODY_SEPARATOR.length);
|
||||||
|
if (!Arrays.equals(BODY_SEPARATOR, sep)) {
|
||||||
|
throw new ConnectorException("Invalid body separator");
|
||||||
|
}
|
||||||
|
|
||||||
|
responseConsumer.accept(res, in);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't communicate with socket", ex);
|
||||||
|
} finally {
|
||||||
|
if (!keepOpen) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <REQ extends RequestMessage, RES extends ResponseMessage> RES simpleExchange(REQ req)
|
||||||
|
throws ServerException, ConnectorException, ClientException {
|
||||||
|
try {
|
||||||
|
sendRequest(req);
|
||||||
|
return this.receiveResponse();
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends RequestMessage> void sendRequest(T req) throws ClientException, ConnectorException {
|
||||||
|
ObjectNode json = JacksonHelper.newMapper().valueToTree(req);
|
||||||
|
var prov = MessageProviders.byRequest(req);
|
||||||
|
if (prov.isEmpty()) {
|
||||||
|
throw new ClientException("Unknown request class " + req.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
json.set("type", new TextNode(prov.get().getId()));
|
||||||
|
json.set("phase", new TextNode("request"));
|
||||||
|
//json.set("id", new TextNode(UUID.randomUUID().toString()));
|
||||||
|
var msg = JsonNodeFactory.instance.objectNode();
|
||||||
|
msg.set("xPipeMessage", json);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
var mapper = JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||||
|
var gen = mapper.createGenerator(socket.getOutputStream());
|
||||||
|
gen.writeTree(msg);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't write to socket", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends ResponseMessage> T receiveResponse() throws ConnectorException, ClientException, ServerException {
|
||||||
|
JsonNode read;
|
||||||
|
try {
|
||||||
|
var in = socket.getInputStream();
|
||||||
|
read = JacksonHelper.newMapper().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE).readTree(in);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't read from socket", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sockets.debugEnabled()) {
|
||||||
|
System.out.println("Recieved response:");
|
||||||
|
System.out.println(read.toPrettyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
var se = parseServerError(read);
|
||||||
|
if (se.isPresent()) {
|
||||||
|
se.get().throwError();
|
||||||
|
}
|
||||||
|
|
||||||
|
var ce = parseClientError(read);
|
||||||
|
if (ce.isPresent()) {
|
||||||
|
throw ce.get().throwException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResponse(read);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientErrorMessage> parseClientError(JsonNode node) throws ConnectorException {
|
||||||
|
ObjectNode content = (ObjectNode) node.get("xPipeClientError");
|
||||||
|
if (content == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var reader = JacksonHelper.newMapper().readerFor(ClientErrorMessage.class);
|
||||||
|
return Optional.of(reader.readValue(content));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't parse client error message", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ServerErrorMessage> parseServerError(JsonNode node) throws ConnectorException {
|
||||||
|
ObjectNode content = (ObjectNode) node.get("xPipeServerError");
|
||||||
|
if (content == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var reader = JacksonHelper.newMapper().readerFor(ServerErrorMessage.class);
|
||||||
|
return Optional.of(reader.readValue(content));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't parse server error message", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends ResponseMessage> T parseResponse(JsonNode header) throws ConnectorException {
|
||||||
|
ObjectNode content = (ObjectNode) header.required("xPipeMessage");
|
||||||
|
|
||||||
|
var type = content.required("type").textValue();
|
||||||
|
var phase = content.required("phase").textValue();
|
||||||
|
//var requestId = UUID.fromString(content.required("id").textValue());
|
||||||
|
if (!phase.equals("response")) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
content.remove("type");
|
||||||
|
content.remove("phase");
|
||||||
|
//content.remove("id");
|
||||||
|
|
||||||
|
var prov = MessageProviders.byId(type);
|
||||||
|
if (prov.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Unknown response id " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var reader = JacksonHelper.newMapper().readerFor(prov.get().getResponseClass());
|
||||||
|
return reader.readValue(content);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't parse response", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
186
beacon/src/main/java/io/xpipe/beacon/socket/SocketServer.java
Normal file
186
beacon/src/main/java/io/xpipe/beacon/socket/SocketServer.java
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
package io.xpipe.beacon.socket;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
|
import io.xpipe.beacon.message.*;
|
||||||
|
import io.xpipe.core.util.JacksonHelper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SocketServer {
|
||||||
|
|
||||||
|
private static final String BEACON_PORT_PROP = "io.xpipe.beacon.port";
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SocketServer.class);
|
||||||
|
|
||||||
|
private static final int DEFAULT_PORT = 21721;
|
||||||
|
private static SocketServer INSTANCE;
|
||||||
|
private final int port;
|
||||||
|
private ServerSocket socket;
|
||||||
|
private boolean running;
|
||||||
|
private int connectionCounter;
|
||||||
|
|
||||||
|
private SocketServer(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path getUserDir() {
|
||||||
|
return Path.of(System.getProperty("user.home"), ".xpipe");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int determineUsedPort() {
|
||||||
|
if (System.getProperty(BEACON_PORT_PROP) != null) {
|
||||||
|
return Integer.parseInt(System.getProperty(BEACON_PORT_PROP));
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = getUserDir().resolve("port");
|
||||||
|
if (Files.exists(file)) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(Files.readString(file));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init() throws IOException {
|
||||||
|
var port = determineUsedPort();
|
||||||
|
INSTANCE = new SocketServer(port);
|
||||||
|
INSTANCE.createSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
INSTANCE.stop();
|
||||||
|
INSTANCE = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSocket() throws IOException {
|
||||||
|
socket = new ServerSocket(port, 1000, InetAddress.getLoopbackAddress());
|
||||||
|
running = true;
|
||||||
|
var t = new Thread(() -> {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
var clientSocket = socket.accept();
|
||||||
|
handleClientConnection(clientSocket);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
connectionCounter++;
|
||||||
|
}
|
||||||
|
}, "socket server");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleClientConnection(Socket clientSocket) {
|
||||||
|
var t = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
var in = clientSocket.getInputStream();
|
||||||
|
var read = JacksonHelper.newMapper().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE).readTree(in);
|
||||||
|
logger.debug("Received request: \n" + read.toPrettyString());
|
||||||
|
|
||||||
|
var req = parseRequest(read);
|
||||||
|
var prov = MessageProviders.byRequest(req).get();
|
||||||
|
prov.onRequestReceived(this, req, in, clientSocket);
|
||||||
|
} catch (SocketException ex) {
|
||||||
|
try {
|
||||||
|
ex.printStackTrace();
|
||||||
|
} catch (Exception ioex) {
|
||||||
|
ioex.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
try {
|
||||||
|
ex.printStackTrace();
|
||||||
|
sendServerErrorResponse(clientSocket, ex);
|
||||||
|
} catch (Exception ioex) {
|
||||||
|
ioex.printStackTrace();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
clientSocket.close();
|
||||||
|
} catch (Exception ioex) {
|
||||||
|
ioex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "socket connection #" + connectionCounter);
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prepareBody(Socket outSocket) throws IOException {
|
||||||
|
outSocket.getOutputStream().write(Sockets.BODY_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends ResponseMessage> void sendResponse(Socket outSocket, T obj) throws Exception {
|
||||||
|
ObjectNode json = JacksonHelper.newMapper().valueToTree(obj);
|
||||||
|
var prov = MessageProviders.byResponse(obj).get();
|
||||||
|
json.set("type", new TextNode(prov.getId()));
|
||||||
|
json.set("phase", new TextNode("response"));
|
||||||
|
var msg = JsonNodeFactory.instance.objectNode();
|
||||||
|
msg.set("xPipeMessage", json);
|
||||||
|
|
||||||
|
var mapper = JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||||
|
var gen = mapper.createGenerator(outSocket.getOutputStream());
|
||||||
|
gen.writeTree(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendClientErrorResponse(Socket outSocket, String message) throws Exception {
|
||||||
|
var err = new ClientErrorMessage(message);
|
||||||
|
ObjectNode json = JacksonHelper.newMapper().valueToTree(err);
|
||||||
|
var msg = JsonNodeFactory.instance.objectNode();
|
||||||
|
msg.set("xPipeClientError", json);
|
||||||
|
|
||||||
|
var mapper = JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||||
|
var gen = mapper.createGenerator(outSocket.getOutputStream());
|
||||||
|
gen.writeTree(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendServerErrorResponse(Socket outSocket, Throwable ex) throws Exception {
|
||||||
|
var err = new ServerErrorMessage(UUID.randomUUID(), ex);
|
||||||
|
ObjectNode json = JacksonHelper.newMapper().valueToTree(err);
|
||||||
|
var msg = JsonNodeFactory.instance.objectNode();
|
||||||
|
msg.set("xPipeServerError", json);
|
||||||
|
|
||||||
|
var mapper = JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||||
|
var gen = mapper.createGenerator(outSocket.getOutputStream());
|
||||||
|
gen.writeTree(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends RequestMessage> T parseRequest(JsonNode header) throws Exception {
|
||||||
|
ObjectNode content = (ObjectNode) header.required("xPipeMessage");
|
||||||
|
|
||||||
|
var type = content.required("type").textValue();
|
||||||
|
var phase = content.required("phase").textValue();
|
||||||
|
if (!phase.equals("request")) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
content.remove("type");
|
||||||
|
content.remove("phase");
|
||||||
|
|
||||||
|
var prov = MessageProviders.byId(type);
|
||||||
|
if (prov.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader = JacksonHelper.newMapper().readerFor(prov.get().getRequestClass());
|
||||||
|
return reader.readValue(content);
|
||||||
|
}
|
||||||
|
}
|
16
beacon/src/main/java/io/xpipe/beacon/socket/Sockets.java
Normal file
16
beacon/src/main/java/io/xpipe/beacon/socket/Sockets.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package io.xpipe.beacon.socket;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class Sockets {
|
||||||
|
|
||||||
|
public static final byte[] BODY_SEPARATOR = "\n\n".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final String DEBUG_PROP = "io.xpipe.beacon.debugOutput";
|
||||||
|
|
||||||
|
public static boolean debugEnabled() {
|
||||||
|
if (System.getProperty(DEBUG_PROP) != null) {
|
||||||
|
return Boolean.parseBoolean(System.getProperty(DEBUG_PROP));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
28
beacon/src/main/java/module-info.java
Normal file
28
beacon/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import io.xpipe.app.core.BeaconProvider;
|
||||||
|
import io.xpipe.beacon.BeaconProviderImpl;
|
||||||
|
import io.xpipe.beacon.message.MessageProvider;
|
||||||
|
import io.xpipe.beacon.message.impl.*;
|
||||||
|
|
||||||
|
module io.xpipe.beacon {
|
||||||
|
exports io.xpipe.beacon;
|
||||||
|
exports io.xpipe.beacon.message;
|
||||||
|
exports io.xpipe.beacon.message.impl;
|
||||||
|
requires org.slf4j;
|
||||||
|
requires org.slf4j.simple;
|
||||||
|
|
||||||
|
requires com.fasterxml.jackson.core;
|
||||||
|
requires com.fasterxml.jackson.databind;
|
||||||
|
requires com.fasterxml.jackson.module.paramnames;
|
||||||
|
requires io.xpipe.core;
|
||||||
|
|
||||||
|
opens io.xpipe.beacon;
|
||||||
|
opens io.xpipe.beacon.message;
|
||||||
|
opens io.xpipe.beacon.message.impl;
|
||||||
|
exports io.xpipe.beacon.socket;
|
||||||
|
opens io.xpipe.beacon.socket;
|
||||||
|
|
||||||
|
requires org.apache.commons.lang;
|
||||||
|
|
||||||
|
uses MessageProvider;
|
||||||
|
provides MessageProvider with ListCollectionsExchange, ListEntriesExchange, ReadTableDataExchange, VersionExchange, StatusExchange, ModeExchange, ReadTableInfoExchange;
|
||||||
|
}
|
9
build.gradle
Normal file
9
build.gradle
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'io.xpipe'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
17
core/build.gradle
Normal file
17
core/build.gradle
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id "org.moditect.gradleplugin" version "1.0.0-rc3"
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
75
core/src/main/java/io/xpipe/core/data/DataStructureNode.java
Normal file
75
core/src/main/java/io/xpipe/core/data/DataStructureNode.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package io.xpipe.core.data;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.type.DataType;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
||||||
|
|
||||||
|
protected abstract String getName();
|
||||||
|
|
||||||
|
private UnsupportedOperationException unuspported(String s) {
|
||||||
|
return new UnsupportedOperationException(getName() + " does not support " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTuple() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isArray() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
throw unuspported("size computation");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract DataType getDataType();
|
||||||
|
|
||||||
|
public DataStructureNode at(int index) {
|
||||||
|
throw unuspported("integer indexing");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataStructureNode forKey(String name) {
|
||||||
|
throw unuspported("name indexing");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DataStructureNode> forKeyIfPresent(String name) {
|
||||||
|
throw unuspported("name indexing");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int asInt() {
|
||||||
|
throw unuspported("integer conversion");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String asString() {
|
||||||
|
throw unuspported("string conversion");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<DataStructureNode> stream() {
|
||||||
|
throw unuspported("stream creation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super DataStructureNode> action) {
|
||||||
|
throw unuspported("for each");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Spliterator<DataStructureNode> spliterator() {
|
||||||
|
throw unuspported("spliterator creation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<DataStructureNode> iterator() {
|
||||||
|
throw unuspported("iterator creation");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.xpipe.core.data;
|
||||||
|
|
||||||
|
public interface DataStructureNodeAcceptor<T extends DataStructureNode> {
|
||||||
|
|
||||||
|
boolean accept(T node) throws Exception;
|
||||||
|
}
|
71
core/src/main/java/io/xpipe/core/data/generic/ArrayNode.java
Normal file
71
core/src/main/java/io/xpipe/core/data/generic/ArrayNode.java
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.DataStructureNode;
|
||||||
|
import io.xpipe.core.data.type.ArrayType;
|
||||||
|
import io.xpipe.core.data.type.DataType;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class ArrayNode extends DataStructureNode {
|
||||||
|
|
||||||
|
private final List<DataStructureNode> valueNodes;
|
||||||
|
|
||||||
|
private ArrayNode(List<DataStructureNode> valueNodes) {
|
||||||
|
this.valueNodes = valueNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayNode wrap(List<DataStructureNode> valueNodes) {
|
||||||
|
return new ArrayNode(valueNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayNode copy(List<DataStructureNode> valueNodes) {
|
||||||
|
return new ArrayNode(new ArrayList<>(valueNodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<DataStructureNode> stream() {
|
||||||
|
return Collections.unmodifiableList(valueNodes).stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isArray() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return valueNodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getName() {
|
||||||
|
return "array node";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataType getDataType() {
|
||||||
|
return ArrayType.of(valueNodes.stream().map(DataStructureNode::getDataType).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode at(int index) {
|
||||||
|
return valueNodes.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super DataStructureNode> action) {
|
||||||
|
valueNodes.forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Spliterator<DataStructureNode> spliterator() {
|
||||||
|
return valueNodes.spliterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<DataStructureNode> iterator() {
|
||||||
|
return valueNodes.iterator();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
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<DataStructureNode> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public interface DataStreamCallback {
|
||||||
|
|
||||||
|
static DataStreamCallback flat(Consumer<byte[]> 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) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class DataStreamReader {
|
||||||
|
|
||||||
|
private static final int TUPLE_ID = 1;
|
||||||
|
private static final int ARRAY_ID = 2;
|
||||||
|
private static final int VALUE_ID = 3;
|
||||||
|
|
||||||
|
public static void read(InputStream in, DataStreamCallback cb) throws IOException {
|
||||||
|
var b = in.read();
|
||||||
|
switch (b) {
|
||||||
|
case TUPLE_ID -> {
|
||||||
|
readTuple(in, cb);
|
||||||
|
}
|
||||||
|
case ARRAY_ID -> {
|
||||||
|
readArray(in, cb);
|
||||||
|
}
|
||||||
|
case VALUE_ID -> {
|
||||||
|
readValue(in, cb);
|
||||||
|
}
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readName(InputStream in) throws IOException {
|
||||||
|
var nameLength = in.read();
|
||||||
|
return new String(in.readNBytes(nameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readTuple(InputStream in, DataStreamCallback cb) throws IOException {
|
||||||
|
var name = readName(in);
|
||||||
|
var size = in.read();
|
||||||
|
cb.onTupleStart(name, 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);
|
||||||
|
var size = in.read();
|
||||||
|
cb.onArrayStart(name, 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);
|
||||||
|
var size = in.read();
|
||||||
|
var data = in.readNBytes(size);
|
||||||
|
cb.onValue(name, data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.DataStructureNode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DataStructureNodePointer {
|
||||||
|
|
||||||
|
private final List<Element> path;
|
||||||
|
|
||||||
|
public DataStructureNodePointer(List<Element> path) {
|
||||||
|
this.path = path;
|
||||||
|
|
||||||
|
if (path.size() == 0) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder fromBase(DataStructureNodePointer pointer) {
|
||||||
|
return new Builder(pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "/" + path.stream().map(Element::toString).collect(Collectors.joining("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return path.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid(DataStructureNode input) {
|
||||||
|
return get(input) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataStructureNode get(DataStructureNode root) {
|
||||||
|
DataStructureNode current = root;
|
||||||
|
for (Element value : path) {
|
||||||
|
var found = value.tryMatch(current);
|
||||||
|
if (found == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
current = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DataStructureNode> getIfPresent(DataStructureNode root) {
|
||||||
|
return Optional.ofNullable(get(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Element> getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface Element {
|
||||||
|
|
||||||
|
DataStructureNode tryMatch(DataStructureNode n);
|
||||||
|
|
||||||
|
default String getKey(DataStructureNode n) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final record NameElement(String name) implements Element {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode tryMatch(DataStructureNode n) {
|
||||||
|
return n.forKeyIfPresent(name).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey(DataStructureNode n) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final record IndexElement(int index) implements Element {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode tryMatch(DataStructureNode n) {
|
||||||
|
if (n.size() > index) {
|
||||||
|
return n.at(index);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[" + index + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final record SupplierElement(Supplier<String> keySupplier) implements Element {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode tryMatch(DataStructureNode n) {
|
||||||
|
var name = keySupplier.get();
|
||||||
|
if (name != null) {
|
||||||
|
return n.forKeyIfPresent(name).orElse(null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey(DataStructureNode n) {
|
||||||
|
return keySupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[$s]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final record FunctionElement(Function<DataStructureNode, String> keyFunc) implements Element {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode tryMatch(DataStructureNode n) {
|
||||||
|
var name = keyFunc.apply(n);
|
||||||
|
if (name != null) {
|
||||||
|
return n.forKeyIfPresent(name).orElse(null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey(DataStructureNode n) {
|
||||||
|
return keyFunc.apply(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[$s]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final record SelectorElement(Predicate<DataStructureNode> selector) implements Element {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode tryMatch(DataStructureNode n) {
|
||||||
|
var res = n.stream()
|
||||||
|
.filter(selector)
|
||||||
|
.findAny();
|
||||||
|
return res.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[$(...)]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private final List<Element> path;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
this.path = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder(List<Element> path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(DataStructureNodePointer pointer) {
|
||||||
|
this.path = new ArrayList<>(pointer.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder copy() {
|
||||||
|
return new Builder(new ArrayList<>(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Builder name(String name) {
|
||||||
|
path.add(new NameElement(name));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder index(int index) {
|
||||||
|
path.add(new IndexElement(index));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder supplier(Supplier<String> keySupplier) {
|
||||||
|
path.add(new SupplierElement(keySupplier));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder function(Function<DataStructureNode, String> keyFunc) {
|
||||||
|
path.add(new FunctionElement(keyFunc));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder selector(Predicate<DataStructureNode> selector) {
|
||||||
|
path.add(new SelectorElement(selector));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder pointerEvaluation(DataStructureNodePointer pointer) {
|
||||||
|
return pointerEvaluation(pointer, n -> {
|
||||||
|
if (!n.isValue()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return n.asString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder pointerEvaluation(DataStructureNodePointer pointer, Function<DataStructureNode, String> converter) {
|
||||||
|
path.add(new FunctionElement((current) -> {
|
||||||
|
var res = pointer.get(current);
|
||||||
|
if (res != null) {
|
||||||
|
return converter.apply(res);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataStructureNodePointer build() {
|
||||||
|
return new DataStructureNodePointer(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.DataStructureNode;
|
||||||
|
|
||||||
|
public interface DataStructureNodeReader extends DataStreamCallback {
|
||||||
|
|
||||||
|
boolean isDone();
|
||||||
|
|
||||||
|
DataStructureNode create();
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
79
core/src/main/java/io/xpipe/core/data/generic/TupleNode.java
Normal file
79
core/src/main/java/io/xpipe/core/data/generic/TupleNode.java
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.DataStructureNode;
|
||||||
|
import io.xpipe.core.data.type.DataType;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class TupleNode extends DataStructureNode {
|
||||||
|
|
||||||
|
private final List<String> names;
|
||||||
|
private final List<DataStructureNode> nodes;
|
||||||
|
|
||||||
|
private TupleNode(List<String> names, List<DataStructureNode> nodes) {
|
||||||
|
this.names = names;
|
||||||
|
this.nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TupleNode wrap(List<String> names, List<DataStructureNode> nodes) {
|
||||||
|
return new TupleNode(names, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TupleNode copy(List<String> names, List<DataStructureNode> 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getName() {
|
||||||
|
return "tuple node";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode at(int index) {
|
||||||
|
return nodes.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNode forKey(String name) {
|
||||||
|
return nodes.get(names.indexOf(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DataStructureNode> forKeyIfPresent(String name) {
|
||||||
|
if (!names.contains(name)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(nodes.get(names.indexOf(name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String nameAt(int index) {
|
||||||
|
return names.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getNames() {
|
||||||
|
return Collections.unmodifiableList(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DataStructureNode> getNodes() {
|
||||||
|
return Collections.unmodifiableList(nodes);
|
||||||
|
}
|
||||||
|
}
|
119
core/src/main/java/io/xpipe/core/data/generic/TupleReader.java
Normal file
119
core/src/main/java/io/xpipe/core/data/generic/TupleReader.java
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
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<String> names;
|
||||||
|
private final List<DataStructureNode> 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);
|
||||||
|
}
|
||||||
|
}
|
47
core/src/main/java/io/xpipe/core/data/generic/ValueNode.java
Normal file
47
core/src/main/java/io/xpipe/core/data/generic/ValueNode.java
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package io.xpipe.core.data.generic;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.DataStructureNode;
|
||||||
|
import io.xpipe.core.data.type.DataType;
|
||||||
|
import io.xpipe.core.data.type.ValueType;
|
||||||
|
|
||||||
|
public class ValueNode extends DataStructureNode {
|
||||||
|
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
private ValueNode(byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValueNode wrap(byte[] data) {
|
||||||
|
return new ValueNode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValue() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int asInt() {
|
||||||
|
return Integer.parseInt(asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String asString() {
|
||||||
|
return new String(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getName() {
|
||||||
|
return "value node";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataType getDataType() {
|
||||||
|
return new ValueType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
58
core/src/main/java/io/xpipe/core/data/type/ArrayType.java
Normal file
58
core/src/main/java/io/xpipe/core/data/type/ArrayType.java
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import io.xpipe.core.data.type.callback.DataTypeCallback;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@JsonTypeName("array")
|
||||||
|
public class ArrayType implements DataType {
|
||||||
|
|
||||||
|
public static ArrayType of(List<DataType> types) {
|
||||||
|
if (types.size() == 0) {
|
||||||
|
return new ArrayType(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = types.get(0);
|
||||||
|
var eq = types.stream().allMatch(d -> d.equals(first));
|
||||||
|
return new ArrayType(eq ? first : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DataType sharedType;
|
||||||
|
|
||||||
|
public ArrayType(DataType sharedType) {
|
||||||
|
this.sharedType = sharedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSimple() {
|
||||||
|
return hasSharedType() && getSharedType().isValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSharedType() {
|
||||||
|
return sharedType != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataType getSharedType() {
|
||||||
|
return sharedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTuple() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isArray() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void traverseType(DataTypeCallback cb) {
|
||||||
|
cb.onArray(this);
|
||||||
|
}
|
||||||
|
}
|
16
core/src/main/java/io/xpipe/core/data/type/DataType.java
Normal file
16
core/src/main/java/io/xpipe/core/data/type/DataType.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import io.xpipe.core.data.type.callback.DataTypeCallback;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
|
public interface DataType {
|
||||||
|
|
||||||
|
boolean isTuple();
|
||||||
|
|
||||||
|
boolean isArray();
|
||||||
|
|
||||||
|
boolean isValue();
|
||||||
|
|
||||||
|
void traverseType(DataTypeCallback cb);
|
||||||
|
}
|
69
core/src/main/java/io/xpipe/core/data/type/TupleType.java
Normal file
69
core/src/main/java/io/xpipe/core/data/type/TupleType.java
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import io.xpipe.core.data.type.callback.DataTypeCallback;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@JsonTypeName("tuple")
|
||||||
|
public class TupleType implements DataType {
|
||||||
|
|
||||||
|
private List<String> names;
|
||||||
|
private List<DataType> types;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
private TupleType(List<String> names, List<DataType> types) {
|
||||||
|
this.names = names;
|
||||||
|
this.types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TupleType empty() {
|
||||||
|
return new TupleType(List.of(), List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TupleType wrap(List<String> names, List<DataType> types) {
|
||||||
|
return new TupleType(names, types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TupleType wrapWithoutNames(List<DataType> types) {
|
||||||
|
return new TupleType(Collections.nCopies(types.size(), null), types);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTuple() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isArray() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void traverseType(DataTypeCallback cb) {
|
||||||
|
cb.onTupleBegin(this);
|
||||||
|
for (var t : types) {
|
||||||
|
t.traverseType(cb);
|
||||||
|
}
|
||||||
|
cb.onTupleEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return types.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getNames() {
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DataType> getTypes() {
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.type.callback.TypedDataStreamCallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class TypedDataStreamReader {
|
||||||
|
|
||||||
|
private static final int STRUCTURE_ID = 0;
|
||||||
|
private static final int TUPLE_ID = 1;
|
||||||
|
private static final int ARRAY_ID = 2;
|
||||||
|
private static final int VALUE_ID = 3;
|
||||||
|
|
||||||
|
public static boolean hasNext(InputStream in) throws IOException {
|
||||||
|
var b = in.read();
|
||||||
|
if (b == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b != STRUCTURE_ID) {
|
||||||
|
throw new IOException("Unexpected value: " + b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readStructures(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
|
while (true) {
|
||||||
|
if (!hasNext(in)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.onNodeBegin();
|
||||||
|
read(in, cb);
|
||||||
|
cb.onNodeEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readStructure(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
|
if (!hasNext(in)) {
|
||||||
|
throw new IllegalStateException("No structure to read");
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.onNodeBegin();
|
||||||
|
read(in, cb);
|
||||||
|
cb.onNodeEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void read(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
|
var b = in.read();
|
||||||
|
|
||||||
|
// Skip
|
||||||
|
if (b == STRUCTURE_ID) {
|
||||||
|
b = in.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (b) {
|
||||||
|
case TUPLE_ID -> {
|
||||||
|
readTuple(in, cb);
|
||||||
|
}
|
||||||
|
case ARRAY_ID -> {
|
||||||
|
readArray(in, cb);
|
||||||
|
}
|
||||||
|
case VALUE_ID -> {
|
||||||
|
readValue(in, cb);
|
||||||
|
}
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readTuple(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
|
var size = in.read();
|
||||||
|
cb.onTupleBegin(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
read(in, cb);
|
||||||
|
}
|
||||||
|
cb.onTupleEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readArray(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
|
var size = in.read();
|
||||||
|
cb.onArrayBegin(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
read(in, cb);
|
||||||
|
}
|
||||||
|
cb.onArrayEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readValue(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
|
var size = in.read();
|
||||||
|
var data = in.readNBytes(size);
|
||||||
|
cb.onValue(data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class TypedDataStreamWriter {
|
||||||
|
|
||||||
|
private static final int STRUCTURE_ID = 0;
|
||||||
|
private static final int TUPLE_ID = 1;
|
||||||
|
private static final int ARRAY_ID = 2;
|
||||||
|
private static final int VALUE_ID = 3;
|
||||||
|
|
||||||
|
public static void writeStructure(OutputStream out, DataStructureNode node) throws IOException {
|
||||||
|
out.write(STRUCTURE_ID);
|
||||||
|
write(out, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
|
||||||
|
out.write(VALUE_ID);
|
||||||
|
out.write(n.getRawData().length);
|
||||||
|
out.write(n.getRawData());
|
||||||
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
core/src/main/java/io/xpipe/core/data/type/ValueType.java
Normal file
30
core/src/main/java/io/xpipe/core/data/type/ValueType.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import io.xpipe.core.data.type.callback.DataTypeCallback;
|
||||||
|
|
||||||
|
@JsonTypeName("value")
|
||||||
|
public class ValueType implements DataType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTuple() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public boolean isArray() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValue() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void traverseType(DataTypeCallback cb) {
|
||||||
|
cb.onValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.type.ArrayType;
|
||||||
|
import io.xpipe.core.data.type.DataType;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
import io.xpipe.core.data.type.ValueType;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public interface DataTypeCallback {
|
||||||
|
|
||||||
|
public static DataTypeCallback flatten(Consumer<DataType> typeConsumer) {
|
||||||
|
return new DataTypeCallback() {
|
||||||
|
@Override
|
||||||
|
public void onValue() {
|
||||||
|
typeConsumer.accept(new ValueType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleBegin(TupleType tuple) {
|
||||||
|
typeConsumer.accept(tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArray(ArrayType type) {
|
||||||
|
typeConsumer.accept(type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onTupleBegin(TupleType tuple) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onTupleEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onArray(ArrayType type) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.generic.DataStructureNodePointer;
|
||||||
|
import io.xpipe.core.data.type.ArrayType;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class DataTypeCallbacks {
|
||||||
|
|
||||||
|
public static DataTypeCallback visitTuples(Consumer<String> newTuple, Runnable endTuple, BiConsumer<String, DataStructureNodePointer> newValue) {
|
||||||
|
return new DataTypeCallback() {
|
||||||
|
|
||||||
|
private final Stack<String> keyNames = new Stack<>();
|
||||||
|
private final Stack<DataStructureNodePointer.Builder> builders = new Stack<>();
|
||||||
|
|
||||||
|
{
|
||||||
|
builders.push(DataStructureNodePointer.builder());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnTopLevel() {
|
||||||
|
return keyNames.size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleBegin(TupleType tuple) {
|
||||||
|
if (!isOnTopLevel()) {
|
||||||
|
newTuple.accept(keyNames.peek());
|
||||||
|
}
|
||||||
|
tuple.getNames().forEach(n -> {
|
||||||
|
keyNames.push(n);
|
||||||
|
builders.push(builders.peek().copy().name(n));
|
||||||
|
tuple.getTypes().forEach(dt -> dt.traverseType(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValue() {
|
||||||
|
newValue.accept(keyNames.peek(), builders.peek().build());
|
||||||
|
keyNames.pop();
|
||||||
|
builders.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleEnd() {
|
||||||
|
endTuple.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArray(ArrayType type) {
|
||||||
|
if (!type.isSimple()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
newValue.accept(keyNames.peek(), builders.peek().build());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
|
||||||
|
public class FlatArrayTypeCallback implements DataTypeCallback {
|
||||||
|
|
||||||
|
private final FlatCallback cb;
|
||||||
|
private int arrayDepth = 0;
|
||||||
|
|
||||||
|
public FlatArrayTypeCallback(FlatCallback cb) {
|
||||||
|
this.cb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInArray() {
|
||||||
|
return arrayDepth > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValue() {
|
||||||
|
if (isInArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.onValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleBegin(TupleType tuple) {
|
||||||
|
if (isInArray()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.onTupleBegin(tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleEnd() {
|
||||||
|
cb.onTupleEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onArray() {
|
||||||
|
if (isInArray()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayDepth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface FlatCallback {
|
||||||
|
|
||||||
|
default void onValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onTupleBegin(TupleType tuple) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onTupleEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onFlatArray() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
public class ReusableTypedDataStructureNodeCallback implements TypedDataStreamCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValue(byte[] data) {
|
||||||
|
TypedDataStreamCallback.super.onValue(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleBegin(int size) {
|
||||||
|
TypedDataStreamCallback.super.onTupleBegin(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleEnd() {
|
||||||
|
TypedDataStreamCallback.super.onTupleEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArrayBegin(int size) {
|
||||||
|
TypedDataStreamCallback.super.onArrayBegin(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArrayEnd() {
|
||||||
|
TypedDataStreamCallback.super.onArrayEnd();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.generic.DataStructureNodePointer;
|
||||||
|
import io.xpipe.core.data.type.ArrayType;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class TableTypeCallback implements DataTypeCallback {
|
||||||
|
|
||||||
|
private final Stack<TupleType> tuples = new Stack<>();
|
||||||
|
private final Stack<Integer> keyIndices = new Stack<>();
|
||||||
|
private final Consumer<String> newTuple;
|
||||||
|
private final Runnable endTuple;
|
||||||
|
private final BiConsumer<String, DataStructureNodePointer> newValue;
|
||||||
|
|
||||||
|
private TableTypeCallback(Consumer<String> newTuple, Runnable endTuple, BiConsumer<String, DataStructureNodePointer> newValue) {
|
||||||
|
this.newTuple = newTuple;
|
||||||
|
this.endTuple = endTuple;
|
||||||
|
this.newValue = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataTypeCallback create(Consumer<String> newTuple, Runnable endTuple, BiConsumer<String, DataStructureNodePointer> newValue) {
|
||||||
|
return new TableTypeCallback(newTuple, endTuple, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnTopLevel() {
|
||||||
|
return tuples.size() <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAnyValue() {
|
||||||
|
var pointer = DataStructureNodePointer.builder();
|
||||||
|
for (int index : keyIndices) {
|
||||||
|
pointer.index(index);
|
||||||
|
}
|
||||||
|
var p = pointer.build();
|
||||||
|
newValue.accept(tuples.peek().getNames().get(keyIndices.peek()), p);
|
||||||
|
|
||||||
|
moveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveIndex() {
|
||||||
|
var index = keyIndices.pop();
|
||||||
|
index++;
|
||||||
|
keyIndices.push(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValue() {
|
||||||
|
onAnyValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleBegin(TupleType tuple) {
|
||||||
|
if (!isOnTopLevel()) {
|
||||||
|
moveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
tuples.push(tuple);
|
||||||
|
keyIndices.push(0);
|
||||||
|
|
||||||
|
if (!isOnTopLevel()) {
|
||||||
|
newTuple.accept(tuples.peek().getNames().get(keyIndices.peek()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleEnd() {
|
||||||
|
endTuple.run();
|
||||||
|
tuples.pop();
|
||||||
|
keyIndices.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArray(ArrayType type) {
|
||||||
|
onAnyValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
public interface TypedDataStreamCallback {
|
||||||
|
|
||||||
|
default void onValue(byte[] data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onTupleBegin(int size) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onTupleEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onArrayBegin(int size) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onArrayEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onNodeBegin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onNodeEnd() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package io.xpipe.core.data.type.callback;
|
||||||
|
|
||||||
|
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.type.DataType;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class TypedDataStructureNodeCallback implements TypedDataStreamCallback {
|
||||||
|
|
||||||
|
private final List<DataType> flattened;
|
||||||
|
private int dataTypeIndex;
|
||||||
|
private Stack<List<DataStructureNode>> children;
|
||||||
|
private Stack<DataStructureNode> nodes;
|
||||||
|
private DataStructureNode readNode;
|
||||||
|
private final Consumer<DataStructureNode> consumer;
|
||||||
|
|
||||||
|
public TypedDataStructureNodeCallback(DataType type, Consumer<DataStructureNode> consumer) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
flattened = new ArrayList<>();
|
||||||
|
children = new Stack<>();
|
||||||
|
nodes = new Stack<>();
|
||||||
|
type.traverseType(DataTypeCallback.flatten(d -> flattened.add(d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNodeBegin() {
|
||||||
|
if (nodes.size() != 0 || children.size() != 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTypeIndex = 0;
|
||||||
|
readNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNodeEnd() {
|
||||||
|
if (nodes.size() != 0 || children.size() != 0 || readNode == null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
consumer.accept(readNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValue(byte[] data) {
|
||||||
|
children.peek().add(ValueNode.wrap(data));
|
||||||
|
if (!flattened.get(dataTypeIndex).isArray()) {
|
||||||
|
dataTypeIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void newTuple() {
|
||||||
|
TupleType tupleType = (TupleType) flattened.get(dataTypeIndex);
|
||||||
|
var l = new ArrayList<DataStructureNode>(tupleType.getSize());
|
||||||
|
children.push(l);
|
||||||
|
var newNode = TupleNode.wrap(tupleType.getNames(), l);
|
||||||
|
nodes.push(newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void newArray() {
|
||||||
|
var l = new ArrayList<DataStructureNode>();
|
||||||
|
children.push(new ArrayList<>());
|
||||||
|
var newNode = ArrayNode.wrap(l);
|
||||||
|
nodes.push(newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishTuple() {
|
||||||
|
children.pop();
|
||||||
|
dataTypeIndex++;
|
||||||
|
var popped = nodes.pop();
|
||||||
|
if (!popped.isTuple()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
TupleNode tuple = (TupleNode) popped;
|
||||||
|
if (tuple.getNames().size() != tuple.getNodes().size()) {
|
||||||
|
throw new IllegalStateException("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.empty()) {
|
||||||
|
readNode = popped;
|
||||||
|
} else {
|
||||||
|
children.peek().add(popped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishArray() {
|
||||||
|
children.pop();
|
||||||
|
dataTypeIndex++;
|
||||||
|
var popped = nodes.pop();
|
||||||
|
if (nodes.empty()) {
|
||||||
|
readNode = popped;
|
||||||
|
} else {
|
||||||
|
children.peek().add(popped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleBegin(int size) {
|
||||||
|
if (!flattened.get(dataTypeIndex).isTuple()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
newTuple();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTupleEnd() {
|
||||||
|
finishTuple();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArrayBegin(int size) {
|
||||||
|
if (!flattened.get(dataTypeIndex).isArray()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
newArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArrayEnd() {
|
||||||
|
finishArray();
|
||||||
|
}
|
||||||
|
}
|
14
core/src/main/java/io/xpipe/core/source/DataSource.java
Normal file
14
core/src/main/java/io/xpipe/core/source/DataSource.java
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface DataSource<DS extends DataStore> {
|
||||||
|
|
||||||
|
default Optional<String> determineDefaultName(DS store) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSourceType getType();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
public interface DataSourceConnection extends AutoCloseable {
|
||||||
|
|
||||||
|
void init() throws Exception;
|
||||||
|
|
||||||
|
void close() throws Exception;
|
||||||
|
}
|
55
core/src/main/java/io/xpipe/core/source/DataSourceId.java
Normal file
55
core/src/main/java/io/xpipe/core/source/DataSourceId.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
|
||||||
|
public class DataSourceId {
|
||||||
|
|
||||||
|
public static final char SEPARATOR = ':';
|
||||||
|
|
||||||
|
private final String collectionName;
|
||||||
|
private final String entryName;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public DataSourceId(String collectionName, String entryName) {
|
||||||
|
this.collectionName = collectionName;
|
||||||
|
this.entryName = entryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSourceId withEntryName(String newName) {
|
||||||
|
return new DataSourceId(collectionName, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSourceId fromString(String s) {
|
||||||
|
var split = s.split(String.valueOf(SEPARATOR));
|
||||||
|
if (split.length != 2) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split[1].length() == 0) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataSourceId(split[0].length() > 0 ? split[0] : null, split[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCollection() {
|
||||||
|
return collectionName != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
12
core/src/main/java/io/xpipe/core/source/DataSourceType.java
Normal file
12
core/src/main/java/io/xpipe/core/source/DataSourceType.java
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public enum DataSourceType {
|
||||||
|
|
||||||
|
@JsonProperty("table")
|
||||||
|
TABLE,
|
||||||
|
|
||||||
|
@JsonProperty("structure")
|
||||||
|
STRUCTURE
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
public interface DataStructureConnection extends DataSourceConnection {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
public abstract class DataStructureSource implements DataSource {
|
||||||
|
|
||||||
|
public abstract DataSourceConnection openConnection() throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceType getType() {
|
||||||
|
return DataSourceType.STRUCTURE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
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.type.TupleType;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public interface DataTableConnection extends DataSourceConnection {
|
||||||
|
|
||||||
|
TupleType determineDataType() throws Exception;
|
||||||
|
|
||||||
|
int determineRowCount() throws Exception;
|
||||||
|
|
||||||
|
void withLines(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception;
|
||||||
|
|
||||||
|
ArrayNode readLines(int maxLines) throws Exception;
|
||||||
|
|
||||||
|
void forwardLines(OutputStream out, int maxLines) throws Exception;
|
||||||
|
}
|
15
core/src/main/java/io/xpipe/core/source/DataTableSource.java
Normal file
15
core/src/main/java/io/xpipe/core/source/DataTableSource.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
|
||||||
|
public abstract class DataTableSource<DS extends DataStore> implements DataSource<DS> {
|
||||||
|
|
||||||
|
public abstract DataTableWriteConnection openWriteConnection(DS store);
|
||||||
|
|
||||||
|
public abstract DataTableConnection openConnection(DS store);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceType getType() {
|
||||||
|
return DataSourceType.TABLE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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<TupleNode> writeLinesAcceptor();
|
||||||
|
|
||||||
|
void writeLines(ArrayNode lines) throws Exception;
|
||||||
|
}
|
18
core/src/main/java/io/xpipe/core/store/DataStore.java
Normal file
18
core/src/main/java/io/xpipe/core/store/DataStore.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
|
public interface DataStore {
|
||||||
|
|
||||||
|
default Optional<String> determineDefaultName() {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Optional<Instant> getLastModified() {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
17
core/src/main/java/io/xpipe/core/store/FileDataInput.java
Normal file
17
core/src/main/java/io/xpipe/core/store/FileDataInput.java
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
public abstract class FileDataInput implements StreamDataStore {
|
||||||
|
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public abstract boolean isLocal();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public abstract LocalFileDataInput getLocal();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public abstract RemoteFileDataInput getRemote();
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public abstract class InputStreamDataStore implements StreamDataStore {
|
||||||
|
|
||||||
|
private final InputStream in;
|
||||||
|
|
||||||
|
public InputStreamDataStore(InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream openInput() throws Exception {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream openOutput() throws Exception {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.*;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@JsonTypeName("local")
|
||||||
|
public class LocalFileDataInput extends FileDataInput {
|
||||||
|
|
||||||
|
private final Path file;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public LocalFileDataInput(Path file) {
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> determineDefaultName() {
|
||||||
|
return Optional.of(FilenameUtils.getBaseName(file.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Instant> getLastModified() {
|
||||||
|
try {
|
||||||
|
var l = Files.getLastModifiedTime(file);
|
||||||
|
return Optional.of(l.toInstant());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return file.getFileName().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalFileDataInput getLocal() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteFileDataInput getRemote() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream openInput() throws Exception {
|
||||||
|
return Files.newInputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream openOutput() throws Exception {
|
||||||
|
return Files.newOutputStream(file);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class RemoteFileDataInput extends FileDataInput {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> determineDefaultName() {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Instant> getLastModified() {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalFileDataInput getLocal() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteFileDataInput getRemote() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream openInput() throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream openOutput() throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
11
core/src/main/java/io/xpipe/core/store/StreamDataStore.java
Normal file
11
core/src/main/java/io/xpipe/core/store/StreamDataStore.java
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public interface StreamDataStore extends DataStore {
|
||||||
|
|
||||||
|
InputStream openInput() throws Exception;
|
||||||
|
|
||||||
|
OutputStream openOutput() throws Exception;
|
||||||
|
}
|
72
core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java
Normal file
72
core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class CoreJacksonModule extends SimpleModule {
|
||||||
|
|
||||||
|
public static class CharsetSerializer extends JsonSerializer<Charset> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(Charset value, JsonGenerator jgen, SerializerProvider provider)
|
||||||
|
throws IOException, JsonProcessingException {
|
||||||
|
jgen.writeString(value.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CharsetDeserializer extends JsonDeserializer<Charset> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Charset deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||||
|
return Charset.forName(p.getValueAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LocalPathSerializer extends JsonSerializer<Path> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(Path value, JsonGenerator jgen, SerializerProvider provider)
|
||||||
|
throws IOException, JsonProcessingException {
|
||||||
|
jgen.writeString(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LocalPathDeserializer extends JsonDeserializer<Path> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||||
|
return Path.of(p.getValueAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupModule(SetupContext context) {
|
||||||
|
context.registerSubtypes(
|
||||||
|
new NamedType(LocalFileDataInput.class),
|
||||||
|
new NamedType(ValueType.class),
|
||||||
|
new NamedType(TupleType.class),
|
||||||
|
new NamedType(ArrayType.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
addSerializer(Charset.class, new CharsetSerializer());
|
||||||
|
addDeserializer(Charset.class, new CharsetDeserializer());
|
||||||
|
|
||||||
|
addSerializer(Path.class, new LocalPathSerializer());
|
||||||
|
addDeserializer(Path.class, new LocalPathDeserializer());
|
||||||
|
}
|
||||||
|
}
|
48
core/src/main/java/io/xpipe/core/util/JacksonHelper.java
Normal file
48
core/src/main/java/io/xpipe/core/util/JacksonHelper.java
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.databind.Module;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
public class JacksonHelper {
|
||||||
|
|
||||||
|
private static final ObjectMapper INSTANCE = new ObjectMapper();
|
||||||
|
private static boolean init = false;
|
||||||
|
|
||||||
|
public static synchronized void init(ModuleLayer layer) {
|
||||||
|
ObjectMapper objectMapper = INSTANCE;
|
||||||
|
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
|
||||||
|
objectMapper.registerModules(findModules(layer));
|
||||||
|
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
|
||||||
|
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
|
||||||
|
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
|
||||||
|
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
|
||||||
|
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
|
||||||
|
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Module> findModules(ModuleLayer layer)
|
||||||
|
{
|
||||||
|
ArrayList<Module> modules = new ArrayList<Module>();
|
||||||
|
ServiceLoader<Module> loader = ServiceLoader.load(layer, Module.class);
|
||||||
|
for (Module module : loader) {
|
||||||
|
modules.add(module);
|
||||||
|
}
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObjectMapper newMapper() {
|
||||||
|
if (!init) {
|
||||||
|
throw new IllegalStateException("Not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return INSTANCE.copy();
|
||||||
|
}
|
||||||
|
}
|
28
core/src/main/java/module-info.java
Normal file
28
core/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import io.xpipe.core.util.CoreJacksonModule;
|
||||||
|
|
||||||
|
module io.xpipe.core {
|
||||||
|
requires com.fasterxml.jackson.core;
|
||||||
|
requires com.fasterxml.jackson.databind;
|
||||||
|
requires com.fasterxml.jackson.module.paramnames;
|
||||||
|
|
||||||
|
exports io.xpipe.core.store;
|
||||||
|
exports io.xpipe.core.source;
|
||||||
|
exports io.xpipe.core.data.generic;
|
||||||
|
exports io.xpipe.core.data.type;
|
||||||
|
|
||||||
|
opens io.xpipe.core.store;
|
||||||
|
opens io.xpipe.core.source;
|
||||||
|
opens io.xpipe.core.data.type;
|
||||||
|
opens io.xpipe.core.data.generic;
|
||||||
|
exports io.xpipe.core.data.type.callback;
|
||||||
|
opens io.xpipe.core.data.type.callback;
|
||||||
|
exports io.xpipe.core.data;
|
||||||
|
opens io.xpipe.core.data;
|
||||||
|
exports io.xpipe.core.util;
|
||||||
|
|
||||||
|
uses com.fasterxml.jackson.databind.Module;
|
||||||
|
provides com.fasterxml.jackson.databind.Module with CoreJacksonModule;
|
||||||
|
|
||||||
|
requires org.apache.commons.lang;
|
||||||
|
requires org.apache.commons.io;
|
||||||
|
}
|
1
deps
Submodule
1
deps
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e7f63e92d05537cee82e320a2017ddc26b9e3d3e
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
185
gradlew
vendored
Normal file
185
gradlew
vendored
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MSYS* | MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
7
settings.gradle
Normal file
7
settings.gradle
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
rootProject.name = 'xpipe_java'
|
||||||
|
|
||||||
|
include 'core'
|
||||||
|
include 'beacon'
|
||||||
|
include 'api'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue