mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Various reworks
This commit is contained in:
parent
4d168b66c0
commit
5459347482
94 changed files with 1186 additions and 514 deletions
|
@ -8,6 +8,9 @@ plugins {
|
||||||
apply from: "$rootDir/deps/java.gradle"
|
apply from: "$rootDir/deps/java.gradle"
|
||||||
apply from: "$rootDir/deps/junit.gradle"
|
apply from: "$rootDir/deps/junit.gradle"
|
||||||
|
|
||||||
|
System.setProperty('excludeExtensionLibrary', 'true')
|
||||||
|
apply from: "$rootDir/scripts/extension_test.gradle"
|
||||||
|
|
||||||
version = file('../misc/version').text
|
version = file('../misc/version').text
|
||||||
group = 'io.xpipe'
|
group = 'io.xpipe'
|
||||||
archivesBaseName = 'xpipe-api'
|
archivesBaseName = 'xpipe-api'
|
||||||
|
@ -30,31 +33,6 @@ configurations {
|
||||||
testImplementation.extendsFrom(dep)
|
testImplementation.extendsFrom(dep)
|
||||||
}
|
}
|
||||||
|
|
||||||
def canTestWithDev = findProject(':app') != null
|
|
||||||
def home = System.getenv('XPIPE_HOME')
|
|
||||||
String daemonCommand = canTestWithDev ?
|
|
||||||
"cmd.exe /c \\\"$rootDir\\gradlew.bat\\\" :app:run" :
|
|
||||||
"cmd.exe /c \\\"$home\\app\\xpipe.exe\\\""
|
|
||||||
|
|
||||||
test {
|
|
||||||
workingDir = rootDir
|
|
||||||
|
|
||||||
// Daemon properties
|
|
||||||
systemProperty "io.xpipe.beacon.exec", daemonCommand +
|
|
||||||
" -Dio.xpipe.app.mode=tray" +
|
|
||||||
" -Dio.xpipe.beacon.port=21722" +
|
|
||||||
" -Dio.xpipe.app.dataDir=$projectDir/local/" +
|
|
||||||
" -Dio.xpipe.storage.persist=false" +
|
|
||||||
" -Dio.xpipe.app.writeSysOut=true" +
|
|
||||||
" -Dio.xpipe.app.writeLogs=true" +
|
|
||||||
// " -Dio.xpipe.beacon.debugOutput=true" +
|
|
||||||
" -Dio.xpipe.app.logLevel=trace"
|
|
||||||
|
|
||||||
// API properties
|
|
||||||
systemProperty 'io.xpipe.beacon.debugOutput', "true"
|
|
||||||
systemProperty 'io.xpipe.beacon.debugExecOutput', "true"
|
|
||||||
systemProperty "io.xpipe.beacon.port", "21722"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: 'publish.gradle'
|
apply from: 'publish.gradle'
|
||||||
apply from: "$rootDir/deps/publish-base.gradle"
|
apply from: "$rootDir/deps/publish-base.gradle"
|
|
@ -20,7 +20,7 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
con.constructSocket();
|
con.constructSocket();
|
||||||
var element = reference.getStart();
|
var element = reference.getStart();
|
||||||
while (true) {
|
while (true) {
|
||||||
if (element.requiresExplicitUserInput()) {
|
if (element != null && element.requiresExplicitUserInput()) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ public abstract class DataSourceImpl implements DataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSource create(DataSourceId id, String type, DataStore store) {
|
public static DataSource create(DataSourceId id, String type, DataStore store) {
|
||||||
if (store instanceof StreamDataStore s && s.isLocalToApplication()) {
|
if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) {
|
||||||
var res = XPipeConnection.execute(con -> {
|
var res = XPipeConnection.execute(con -> {
|
||||||
var req = StoreStreamExchange.Request.builder().build();
|
var req = StoreStreamExchange.Request.builder().build();
|
||||||
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> {
|
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> {
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
package io.xpipe.extension.test;
|
package io.xpipe.api.util;
|
||||||
|
|
||||||
import io.xpipe.api.connector.XPipeConnection;
|
import io.xpipe.api.connector.XPipeConnection;
|
||||||
import io.xpipe.beacon.BeaconClient;
|
import io.xpipe.beacon.BeaconClient;
|
||||||
import io.xpipe.beacon.BeaconServer;
|
import io.xpipe.beacon.BeaconServer;
|
||||||
import io.xpipe.core.charsetter.Charsetter;
|
|
||||||
import io.xpipe.core.charsetter.CharsetterContext;
|
|
||||||
import io.xpipe.core.util.JacksonHelper;
|
|
||||||
import io.xpipe.extension.DataSourceProviders;
|
|
||||||
|
|
||||||
public class ExtensionTestConnector {
|
public class XPipeDaemonController {
|
||||||
|
|
||||||
private static boolean alreadyStarted;
|
private static boolean alreadyStarted;
|
||||||
|
|
||||||
public static void start() throws Exception {
|
public static void start() throws Exception {
|
||||||
DataSourceProviders.init(ModuleLayer.boot());
|
|
||||||
JacksonHelper.initClassBased();
|
|
||||||
Charsetter.init(CharsetterContext.empty());
|
|
||||||
|
|
||||||
if (BeaconServer.isRunning()) {
|
if (BeaconServer.isRunning()) {
|
||||||
alreadyStarted = true;
|
alreadyStarted = true;
|
||||||
return;
|
return;
|
|
@ -1,6 +1,7 @@
|
||||||
module io.xpipe.api {
|
module io.xpipe.api {
|
||||||
exports io.xpipe.api;
|
exports io.xpipe.api;
|
||||||
exports io.xpipe.api.connector;
|
exports io.xpipe.api.connector;
|
||||||
|
exports io.xpipe.api.util;
|
||||||
|
|
||||||
requires transitive io.xpipe.core;
|
requires transitive io.xpipe.core;
|
||||||
requires io.xpipe.beacon;
|
requires io.xpipe.beacon;
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
package io.xpipe.extension.test;
|
package io.xpipe.api.test;
|
||||||
|
|
||||||
|
import io.xpipe.api.util.XPipeDaemonController;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
public class ExtensionTest {
|
public class ApiTest {
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setup() throws Exception {
|
public static void setup() throws Exception {
|
||||||
ExtensionTestConnector.start();
|
XPipeDaemonController.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
public static void teardown() throws Exception {
|
public static void teardown() throws Exception {
|
||||||
ExtensionTestConnector.stop();
|
XPipeDaemonController.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
public class DataTableAccumulatorTest extends DaemonControl {
|
public class DataTableAccumulatorTest extends ApiTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
|
@ -21,13 +21,13 @@ public class DataTableAccumulatorTest extends DaemonControl {
|
||||||
var acc = DataTableAccumulator.create(type);
|
var acc = DataTableAccumulator.create(type);
|
||||||
|
|
||||||
var val = type.convert(
|
var val = type.convert(
|
||||||
TupleNode.of(List.of(ValueNode.ofValue("val1"), ValueNode.ofValue("val2")))).orElseThrow();
|
TupleNode.of(List.of(ValueNode.of("val1"), ValueNode.of("val2")))).orElseThrow();
|
||||||
acc.add(val);
|
acc.add(val);
|
||||||
var table = acc.finish(":test");
|
var table = acc.finish(":test");
|
||||||
|
|
||||||
Assertions.assertEquals(table.getInfo().getDataType(), TupleType.tableType(List.of("col1", "col2")));
|
Assertions.assertEquals(table.getInfo().getDataType(), TupleType.tableType(List.of("col1", "col2")));
|
||||||
Assertions.assertEquals(table.getInfo().getRowCountIfPresent(), OptionalInt.empty());
|
Assertions.assertEquals(table.getInfo().getRowCountIfPresent(), OptionalInt.empty());
|
||||||
var read = table.read(1).at(0);
|
// var read = table.read(1).at(0);
|
||||||
Assertions.assertEquals(val, read);
|
// Assertions.assertEquals(val, read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import io.xpipe.core.source.DataSourceId;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class DataTableTest extends DaemonControl {
|
public class DataTableTest extends ApiTest {
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setupStorage() throws Exception {
|
public static void setupStorage() throws Exception {
|
||||||
|
|
|
@ -9,9 +9,6 @@ apply from: "$rootDir/deps/java.gradle"
|
||||||
apply from: "$rootDir/deps/lombok.gradle"
|
apply from: "$rootDir/deps/lombok.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
|
||||||
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.13.0"
|
|
||||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.13.0"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
version = file('../misc/version').text
|
version = file('../misc/version').text
|
||||||
|
|
|
@ -55,19 +55,19 @@ public class BeaconFormat {
|
||||||
|
|
||||||
private byte[] currentBytes;
|
private byte[] currentBytes;
|
||||||
private int index;
|
private int index;
|
||||||
private boolean finished;
|
private boolean lastBlock;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if ((currentBytes == null || index == currentBytes.length) && !finished) {
|
if ((currentBytes == null || index == currentBytes.length) && !lastBlock) {
|
||||||
readBlock();
|
readBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBytes != null && index == currentBytes.length && finished) {
|
if (currentBytes != null && index == currentBytes.length && lastBlock) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int out = currentBytes[index];
|
int out = currentBytes[index] & 0xff;
|
||||||
index++;
|
index++;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ public class BeaconFormat {
|
||||||
currentBytes = in.readNBytes(lengthInt);
|
currentBytes = in.readNBytes(lengthInt);
|
||||||
index = 0;
|
index = 0;
|
||||||
if (lengthInt < SEGMENT_SIZE) {
|
if (lengthInt < SEGMENT_SIZE) {
|
||||||
finished = true;
|
lastBlock = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,13 +6,14 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/deps/java.gradle"
|
apply from: "$rootDir/deps/java.gradle"
|
||||||
apply from: "$rootDir/deps/jackson.gradle"
|
|
||||||
apply from: "$rootDir/deps/lombok.gradle"
|
apply from: "$rootDir/deps/lombok.gradle"
|
||||||
apply from: "$rootDir/deps/junit.gradle"
|
apply from: "$rootDir/deps/junit.gradle"
|
||||||
|
|
||||||
configurations {
|
|
||||||
compileOnly.extendsFrom(dep)
|
dependencies{
|
||||||
testImplementation.extendsFrom(dep)
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
||||||
|
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.13.0"
|
||||||
|
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.13.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
version = file('../misc/version').text
|
version = file('../misc/version').text
|
||||||
|
|
|
@ -55,13 +55,13 @@ public class GenericDataStreamParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseName(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
private static void parseName(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
||||||
var nameLength = in.read();
|
var nameLength = DataStructureNodeIO.parseShort(in);
|
||||||
var name = new String(in.readNBytes(nameLength));
|
var name = new String(in.readNBytes(nameLength));
|
||||||
cb.onName(name);
|
cb.onName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseTuple(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
private static void parseTuple(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
||||||
var size = in.read();
|
var size = DataStructureNodeIO.parseShort(in);
|
||||||
cb.onTupleStart(size);
|
cb.onTupleStart(size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
parse(in, cb);
|
parse(in, cb);
|
||||||
|
@ -71,7 +71,7 @@ public class GenericDataStreamParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseArray(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
private static void parseArray(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
||||||
var size = in.read();
|
var size = DataStructureNodeIO.parseShort(in);
|
||||||
cb.onArrayStart(size);
|
cb.onArrayStart(size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
parse(in, cb);
|
parse(in, cb);
|
||||||
|
@ -81,7 +81,7 @@ public class GenericDataStreamParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseValue(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
private static void parseValue(InputStream in, GenericDataStreamCallback cb) throws IOException {
|
||||||
var size = in.read();
|
var size = DataStructureNodeIO.parseShort(in);
|
||||||
var data = in.readNBytes(size);
|
var data = in.readNBytes(size);
|
||||||
var attributes = DataStructureNodeIO.parseAttributes(in);
|
var attributes = DataStructureNodeIO.parseAttributes(in);
|
||||||
cb.onValue(data, attributes);
|
cb.onValue(data, attributes);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.core.data.node.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class GenericDataStreamWriter {
|
public class GenericDataStreamWriter {
|
||||||
|
|
||||||
|
@ -27,16 +26,14 @@ public class GenericDataStreamWriter {
|
||||||
|
|
||||||
private static void writeName(OutputStream out, String s) throws IOException {
|
private static void writeName(OutputStream out, String s) throws IOException {
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
var b = s.getBytes(StandardCharsets.UTF_8);
|
|
||||||
out.write(DataStructureNodeIO.GENERIC_NAME_ID);
|
out.write(DataStructureNodeIO.GENERIC_NAME_ID);
|
||||||
out.write(b.length);
|
DataStructureNodeIO.writeString(out, s);
|
||||||
out.write(b);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException {
|
private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException {
|
||||||
out.write(DataStructureNodeIO.GENERIC_TUPLE_ID);
|
out.write(DataStructureNodeIO.GENERIC_TUPLE_ID);
|
||||||
out.write(tuple.size());
|
DataStructureNodeIO.writeShort(out, tuple.size());
|
||||||
for (int i = 0; i < tuple.size(); i++) {
|
for (int i = 0; i < tuple.size(); i++) {
|
||||||
writeName(out, tuple.keyNameAt(i));
|
writeName(out, tuple.keyNameAt(i));
|
||||||
write(out, tuple.at(i));
|
write(out, tuple.at(i));
|
||||||
|
@ -46,7 +43,7 @@ public class GenericDataStreamWriter {
|
||||||
|
|
||||||
private static void writeArray(OutputStream out, ArrayNode array) throws IOException {
|
private static void writeArray(OutputStream out, ArrayNode array) throws IOException {
|
||||||
out.write(DataStructureNodeIO.GENERIC_ARRAY_ID);
|
out.write(DataStructureNodeIO.GENERIC_ARRAY_ID);
|
||||||
out.write(array.size());
|
DataStructureNodeIO.writeShort(out, array.size());
|
||||||
for (int i = 0; i < array.size(); i++) {
|
for (int i = 0; i < array.size(); i++) {
|
||||||
write(out, array.at(i));
|
write(out, array.at(i));
|
||||||
}
|
}
|
||||||
|
@ -55,7 +52,7 @@ public class GenericDataStreamWriter {
|
||||||
|
|
||||||
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
|
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
|
||||||
out.write(DataStructureNodeIO.GENERIC_VALUE_ID);
|
out.write(DataStructureNodeIO.GENERIC_VALUE_ID);
|
||||||
out.write(n.getRawData().length);
|
DataStructureNodeIO.writeShort(out, n.getRawData().length);
|
||||||
out.write(n.getRawData());
|
out.write(n.getRawData());
|
||||||
DataStructureNodeIO.writeAttributes(out, n);
|
DataStructureNodeIO.writeAttributes(out, n);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,17 @@ public abstract class ArrayNode extends DataStructureNode {
|
||||||
if (!(o instanceof ArrayNode that)) {
|
if (!(o instanceof ArrayNode that)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return getNodes().equals(that.getNodes());
|
|
||||||
|
var toReturn = getNodes().equals(that.getNodes()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
|
||||||
|
if (toReturn == false) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(getNodes());
|
return Objects.hash(getNodes(), getMetaAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,7 +57,7 @@ public abstract class ArrayNode extends DataStructureNode {
|
||||||
@Override
|
@Override
|
||||||
public final String toString(int indent) {
|
public final String toString(int indent) {
|
||||||
var content = getNodes().stream().map(n -> n.toString(indent)).collect(Collectors.joining(", "));
|
var content = getNodes().stream().map(n -> n.toString(indent)).collect(Collectors.joining(", "));
|
||||||
return "[" + content + "]";
|
return "[" + content + "] " + metaToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.core.data.type.DataType;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
||||||
|
@ -11,9 +12,15 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
||||||
public static final Integer KEY_TABLE_NAME = 1;
|
public static final Integer KEY_TABLE_NAME = 1;
|
||||||
public static final Integer KEY_ROW_NAME = 2;
|
public static final Integer KEY_ROW_NAME = 2;
|
||||||
public static final Integer BOOLEAN_TRUE = 3;
|
public static final Integer BOOLEAN_TRUE = 3;
|
||||||
public static final Integer BOOLEAN_FALSE = 4;
|
public static final Integer BOOLEAN_VALUE = 4;
|
||||||
public static final Integer INTEGER_VALUE = 5;
|
public static final Integer BOOLEAN_FALSE = 5;
|
||||||
public static final Integer TEXT = 5;
|
public static final Integer INTEGER_VALUE = 6;
|
||||||
|
public static final Integer NULL_VALUE = 7;
|
||||||
|
public static final Integer IS_NUMBER = 8;
|
||||||
|
public static final Integer IS_INTEGER = 9;
|
||||||
|
public static final Integer IS_FLOATING_POINT = 10;
|
||||||
|
public static final Integer FLOATING_POINT_VALUE = 11;
|
||||||
|
public static final Integer TEXT = 12;
|
||||||
|
|
||||||
private Map<Integer, String> metaAttributes;
|
private Map<Integer, String> metaAttributes;
|
||||||
|
|
||||||
|
@ -109,6 +116,15 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
||||||
throw unsupported("clear");
|
throw unsupported("clear");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String metaToString() {
|
||||||
|
return "(" + (metaAttributes != null ?
|
||||||
|
metaAttributes.entrySet()
|
||||||
|
.stream()
|
||||||
|
.map(e -> e.getValue() != null ? e.getKey() + ":" + e.getValue() : e.getKey().toString())
|
||||||
|
.collect(Collectors.joining("|")) :
|
||||||
|
"") + ")";
|
||||||
|
}
|
||||||
|
|
||||||
public abstract String toString(int indent);
|
public abstract String toString(int indent);
|
||||||
|
|
||||||
public boolean isTuple() {
|
public boolean isTuple() {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package io.xpipe.core.data.node;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -20,43 +22,61 @@ public class DataStructureNodeIO {
|
||||||
public static final int TYPED_ARRAY_ID = 7;
|
public static final int TYPED_ARRAY_ID = 7;
|
||||||
public static final int TYPED_VALUE_ID = 8;
|
public static final int TYPED_VALUE_ID = 8;
|
||||||
|
|
||||||
|
public static void writeShort(OutputStream out, int value) throws IOException {
|
||||||
|
var buffer = ByteBuffer.allocate(2);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buffer.putShort((short) value);
|
||||||
|
out.write(buffer.array());
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeString(OutputStream out, String s) throws IOException {
|
public static void writeString(OutputStream out, String s) throws IOException {
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
var b = s.getBytes(StandardCharsets.UTF_8);
|
var b = s.getBytes(StandardCharsets.UTF_8);
|
||||||
out.write(b.length);
|
DataStructureNodeIO.writeShort(out, b.length);
|
||||||
out.write(b);
|
out.write(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static short parseShort(InputStream in) throws IOException {
|
||||||
|
var read = in.readNBytes(2);
|
||||||
|
if (read.length < 2) {
|
||||||
|
throw new IllegalStateException("Unable to read short");
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = ByteBuffer.wrap(read);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
return buffer.getShort();
|
||||||
|
}
|
||||||
|
|
||||||
public static String parseString(InputStream in) throws IOException {
|
public static String parseString(InputStream in) throws IOException {
|
||||||
var nameLength = in.read();
|
var nameLength = parseShort(in);
|
||||||
var name = new String(in.readNBytes(nameLength), StandardCharsets.UTF_8);
|
var name = new String(in.readNBytes(nameLength), StandardCharsets.UTF_8);
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<Integer, String> parseAttributes(InputStream in) throws IOException {
|
public static Map<Integer, String> parseAttributes(InputStream in) throws IOException {
|
||||||
var attributesLength = in.read();
|
var attributesLength = parseShort(in);
|
||||||
if (attributesLength == 0) {
|
if (attributesLength == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var map = new HashMap<Integer, String>();
|
var map = new HashMap<Integer, String>();
|
||||||
for (int i = 0; i < attributesLength; i++) {
|
for (int i = 0; i < attributesLength; i++) {
|
||||||
var key = in.read();
|
var key = parseShort(in);
|
||||||
var value = parseString(in);
|
var value = parseString(in);
|
||||||
map.put(key, value);
|
map.put((int) key, value);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeAttributes(OutputStream out, DataStructureNode s) throws IOException {
|
public static void writeAttributes(OutputStream out, DataStructureNode s) throws IOException {
|
||||||
if (s.getMetaAttributes() != null) {
|
if (s.getMetaAttributes() != null) {
|
||||||
out.write(s.getMetaAttributes().size());
|
writeShort(out, s.getMetaAttributes().size());
|
||||||
for (Map.Entry<Integer, String> entry : s.getMetaAttributes().entrySet()) {
|
for (Map.Entry<Integer, String> entry : s.getMetaAttributes().entrySet()) {
|
||||||
Integer integer = entry.getKey();
|
Integer integer = entry.getKey();
|
||||||
var value = entry.getValue().getBytes(StandardCharsets.UTF_8);
|
var value = entry.getValue().getBytes(StandardCharsets.UTF_8);
|
||||||
out.write(integer);
|
writeShort(out, integer);
|
||||||
out.write(value.length);
|
writeShort(out, value.length);
|
||||||
out.write(value);
|
out.write(value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package io.xpipe.core.data.node;
|
package io.xpipe.core.data.node;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -10,8 +9,9 @@ import java.util.Spliterator;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Value
|
@AllArgsConstructor
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
|
|
||||||
public class SimpleArrayNode extends ArrayNode {
|
public class SimpleArrayNode extends ArrayNode {
|
||||||
|
|
||||||
List<DataStructureNode> nodes;
|
List<DataStructureNode> nodes;
|
||||||
|
@ -79,4 +79,6 @@ public class SimpleArrayNode extends ArrayNode {
|
||||||
nodes.remove(index);
|
nodes.remove(index);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ public class SimpleImmutableValueNode extends ValueNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(int indent) {
|
public String toString(int indent) {
|
||||||
return (hasMetaAttribute(TEXT) ? "\"" : "") + asString() + (hasMetaAttribute(TEXT) ? "\"" : "") + " (I)";
|
var string = getRawData().length == 0 && !hasMetaAttribute(TEXT) ? "<null>" : new String(getRawData(), StandardCharsets.UTF_8);
|
||||||
|
return (hasMetaAttribute(TEXT) ? "\"" : "") + string + (hasMetaAttribute(TEXT) ? "\"" : "") + " " + metaToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,6 +18,7 @@ public class SimpleTupleNode extends TupleNode {
|
||||||
this.names = names;
|
this.names = names;
|
||||||
this.nodes = nodes;
|
this.nodes = nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataStructureNode set(int index, DataStructureNode node) {
|
public DataStructureNode set(int index, DataStructureNode node) {
|
||||||
nodes.set(index, node);
|
nodes.set(index, node);
|
||||||
|
@ -26,7 +27,8 @@ public class SimpleTupleNode extends TupleNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataType determineDataType() {
|
public DataType determineDataType() {
|
||||||
return TupleType.of(names, nodes.stream().map(DataStructureNode::determineDataType).toList());
|
var subtypes = nodes.stream().map(DataStructureNode::determineDataType).toList();
|
||||||
|
return names != null ? TupleType.of(names, subtypes) : TupleType.of(subtypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,7 +48,7 @@ public class SimpleTupleNode extends TupleNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataStructureNode forKey(String name) {
|
public DataStructureNode forKey(String name) {
|
||||||
var index = names.indexOf(name);
|
var index = names != null ? names.indexOf(name) : -1;
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
throw new IllegalArgumentException("Key " + name + " not found");
|
throw new IllegalArgumentException("Key " + name + " not found");
|
||||||
}
|
}
|
||||||
|
@ -56,7 +58,7 @@ public class SimpleTupleNode extends TupleNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<DataStructureNode> forKeyIfPresent(String name) {
|
public Optional<DataStructureNode> forKeyIfPresent(String name) {
|
||||||
if (!names.contains(name)) {
|
if (names == null || !names.contains(name)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +79,10 @@ public class SimpleTupleNode extends TupleNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String keyNameAt(int index) {
|
public String keyNameAt(int index) {
|
||||||
|
if (names == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return names.get(index);
|
return names.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +90,7 @@ public class SimpleTupleNode extends TupleNode {
|
||||||
public List<KeyValue> getKeyValuePairs() {
|
public List<KeyValue> getKeyValuePairs() {
|
||||||
var l = new ArrayList<KeyValue>(size());
|
var l = new ArrayList<KeyValue>(size());
|
||||||
for (int i = 0; i < size(); i++) {
|
for (int i = 0; i < size(); i++) {
|
||||||
l.add(new KeyValue(getKeyNames().get(i), getNodes().get(i)));
|
l.add(new KeyValue(names != null ? getKeyNames().get(i) : null, getNodes().get(i)));
|
||||||
}
|
}
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
public abstract class TupleNode extends DataStructureNode {
|
public abstract class TupleNode extends DataStructureNode {
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
|
@ -19,7 +20,7 @@ public abstract class TupleNode extends DataStructureNode {
|
||||||
return new SimpleTupleNode(null, nodes);
|
return new SimpleTupleNode(null, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TupleNode of(List<String> names, List<DataStructureNode> nodes) {
|
public static TupleNode of(List<String> names, List<? extends DataStructureNode> nodes) {
|
||||||
if (names == null) {
|
if (names == null) {
|
||||||
throw new IllegalArgumentException("Names must be not null");
|
throw new IllegalArgumentException("Names must be not null");
|
||||||
}
|
}
|
||||||
|
@ -30,7 +31,7 @@ public abstract class TupleNode extends DataStructureNode {
|
||||||
throw new IllegalArgumentException("Names and nodes must have the same length");
|
throw new IllegalArgumentException("Names and nodes must have the same length");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleTupleNode(names, nodes);
|
return new SimpleTupleNode(names, (List<DataStructureNode>) nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,12 +60,16 @@ public abstract class TupleNode extends DataStructureNode {
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (!(o instanceof TupleNode that)) return false;
|
if (!(o instanceof TupleNode that)) return false;
|
||||||
return getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes());
|
var toReturn = getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
|
||||||
|
if (toReturn == false) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(getKeyNames(), getNodes());
|
return Objects.hash(getKeyNames(), getNodes(), getMetaAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
|
@ -21,16 +21,20 @@ public abstract class ValueNode extends DataStructureNode {
|
||||||
if (!(o instanceof ValueNode that)) {
|
if (!(o instanceof ValueNode that)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return Arrays.equals(getRawData(), that.getRawData()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
|
var toReturn = Arrays.equals(getRawData(), that.getRawData()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
|
||||||
|
if (toReturn == false) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Arrays.hashCode(getRawData());
|
return Arrays.hashCode(getRawData()) + Objects.hash(getMetaAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueNode nullValue() {
|
public static ValueNode nullValue() {
|
||||||
return new SimpleImmutableValueNode(new byte[0]);
|
return new SimpleImmutableValueNode(new byte[0]).tag(NULL_VALUE).asValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueNode of(byte[] data) {
|
public static ValueNode of(byte[] data) {
|
||||||
|
@ -38,6 +42,10 @@ public abstract class ValueNode extends DataStructureNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValueNode of(Object o) {
|
public static ValueNode of(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return nullValue();
|
||||||
|
}
|
||||||
|
|
||||||
return of(o.toString().getBytes(StandardCharsets.UTF_8));
|
return of(o.toString().getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ public class TypedDataStreamParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseTypedArray(InputStream in, TypedDataStreamCallback cb, ArrayType type) throws IOException {
|
private void parseTypedArray(InputStream in, TypedDataStreamCallback cb, ArrayType type) throws IOException {
|
||||||
var size = in.read();
|
var size = DataStructureNodeIO.parseShort(in);
|
||||||
cb.onArrayBegin(size);
|
cb.onArrayBegin(size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
parse(in, cb, type.getSharedType());
|
parse(in, cb, type.getSharedType());
|
||||||
|
@ -129,7 +129,7 @@ public class TypedDataStreamParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseValue(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
private void parseValue(InputStream in, TypedDataStreamCallback cb) throws IOException {
|
||||||
var size = in.read();
|
var size = DataStructureNodeIO.parseShort(in);
|
||||||
var data = in.readNBytes(size);
|
var data = in.readNBytes(size);
|
||||||
cb.onValue(data);
|
cb.onValue(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class TypedDataStreamWriter {
|
||||||
|
|
||||||
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
|
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
|
||||||
out.write(DataStructureNodeIO.TYPED_VALUE_ID);
|
out.write(DataStructureNodeIO.TYPED_VALUE_ID);
|
||||||
out.write(n.getRawData().length);
|
DataStructureNodeIO.writeShort(out, n.getRawData().length);
|
||||||
out.write(n.getRawData());
|
out.write(n.getRawData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ public class TypedDataStreamWriter {
|
||||||
|
|
||||||
private static void writeArray(OutputStream out, ArrayNode array, ArrayType type) throws IOException {
|
private static void writeArray(OutputStream out, ArrayNode array, ArrayType type) throws IOException {
|
||||||
out.write(DataStructureNodeIO.TYPED_ARRAY_ID);
|
out.write(DataStructureNodeIO.TYPED_ARRAY_ID);
|
||||||
out.write(array.size());
|
DataStructureNodeIO.writeShort(out, array.size());
|
||||||
for (int i = 0; i < array.size(); i++) {
|
for (int i = 0; i < array.size(); i++) {
|
||||||
write(out, array.at(i), type.getSharedType());
|
write(out, array.at(i), type.getSharedType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,6 +335,10 @@ public abstract class Dialog {
|
||||||
@Override
|
@Override
|
||||||
public DialogElement start() throws Exception {
|
public DialogElement start() throws Exception {
|
||||||
active = check.get() ? null : d;
|
active = check.get() ? null : d;
|
||||||
|
if (active == null) {
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
|
||||||
return active != null ? active.start() : null;
|
return active != null ? active.start() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,6 +431,9 @@ public abstract class Dialog {
|
||||||
private Supplier<?> evaluation;
|
private Supplier<?> evaluation;
|
||||||
private final List<Consumer<?>> completion = new ArrayList<>();
|
private final List<Consumer<?>> completion = new ArrayList<>();
|
||||||
|
|
||||||
|
/* TODO: Implement automatic completion mechanism for start as well
|
||||||
|
* In case start returns null, the completion is not automatically done.
|
||||||
|
* */
|
||||||
public abstract DialogElement start() throws Exception;
|
public abstract DialogElement start() throws Exception;
|
||||||
|
|
||||||
public Dialog evaluateTo(Dialog d) {
|
public Dialog evaluateTo(Dialog d) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.extension.util;
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||||
import io.xpipe.core.data.node.TupleNode;
|
import io.xpipe.core.data.node.TupleNode;
|
||||||
|
@ -7,11 +7,12 @@ import io.xpipe.core.source.DataSourceConnection;
|
||||||
import io.xpipe.core.source.DataSourceType;
|
import io.xpipe.core.source.DataSourceType;
|
||||||
import io.xpipe.core.source.TableWriteConnection;
|
import io.xpipe.core.source.TableWriteConnection;
|
||||||
|
|
||||||
public class AppendingTableWriteConnection extends AppendingWriteConnection implements TableWriteConnection {
|
public class PreservingTableWriteConnection extends PreservingWriteConnection implements TableWriteConnection {
|
||||||
|
|
||||||
public AppendingTableWriteConnection(DataSource<?> source, DataSourceConnection connection
|
public PreservingTableWriteConnection(DataSource<?> source, DataSourceConnection connection,
|
||||||
|
boolean append
|
||||||
) {
|
) {
|
||||||
super(DataSourceType.TABLE, source, connection);
|
super(DataSourceType.TABLE, source, append, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -1,16 +1,17 @@
|
||||||
package io.xpipe.extension.util;
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
import io.xpipe.core.source.DataSource;
|
import io.xpipe.core.source.DataSource;
|
||||||
import io.xpipe.core.source.DataSourceConnection;
|
import io.xpipe.core.source.DataSourceConnection;
|
||||||
import io.xpipe.core.source.DataSourceType;
|
import io.xpipe.core.source.DataSourceType;
|
||||||
import io.xpipe.core.source.TextWriteConnection;
|
import io.xpipe.core.source.TextWriteConnection;
|
||||||
|
|
||||||
public class AppendingTextWriteConnection extends AppendingWriteConnection implements TextWriteConnection {
|
public class PreservingTextWriteConnection extends PreservingWriteConnection implements TextWriteConnection {
|
||||||
|
|
||||||
public AppendingTextWriteConnection(
|
public PreservingTextWriteConnection(
|
||||||
DataSource<?> source, DataSourceConnection connection
|
DataSource<?> source, DataSourceConnection connection,
|
||||||
|
boolean append
|
||||||
) {
|
) {
|
||||||
super(DataSourceType.TEXT, source, connection);
|
super(DataSourceType.TEXT, source, append, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -1,29 +1,30 @@
|
||||||
package io.xpipe.extension.util;
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
import io.xpipe.core.source.DataSource;
|
import io.xpipe.core.source.DataSource;
|
||||||
import io.xpipe.core.source.DataSourceConnection;
|
import io.xpipe.core.source.DataSourceConnection;
|
||||||
import io.xpipe.core.source.DataSourceType;
|
import io.xpipe.core.source.DataSourceType;
|
||||||
import io.xpipe.core.store.FileStore;
|
import io.xpipe.core.store.FileStore;
|
||||||
import io.xpipe.extension.DataSourceProviders;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
public class AppendingWriteConnection implements DataSourceConnection {
|
public class PreservingWriteConnection implements DataSourceConnection {
|
||||||
|
|
||||||
private final DataSourceType type;
|
private final DataSourceType type;
|
||||||
private final DataSource<?> source;
|
private final DataSource<?> source;
|
||||||
|
private final boolean append ;
|
||||||
protected final DataSourceConnection connection;
|
protected final DataSourceConnection connection;
|
||||||
|
|
||||||
public AppendingWriteConnection(DataSourceType type, DataSource<?> source, DataSourceConnection connection) {
|
public PreservingWriteConnection(DataSourceType type, DataSource<?> source, boolean append, DataSourceConnection connection) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
this.append = append;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
var temp = Files.createTempFile(null, null);
|
var temp = Files.createTempFile(null, null);
|
||||||
var nativeStore = FileStore.local(temp);
|
var nativeStore = FileStore.local(temp);
|
||||||
var nativeSource = DataSourceProviders.getNativeDataSourceDescriptorForType(type).createDefaultSource(nativeStore);
|
var nativeSource = DataSource.createInternalDataSource(type, nativeStore);
|
||||||
if (source.getStore().canOpen()) {
|
if (source.getStore().canOpen()) {
|
||||||
try (var in = source.openReadConnection(); var out = nativeSource.openWriteConnection()) {
|
try (var in = source.openReadConnection(); var out = nativeSource.openWriteConnection()) {
|
||||||
in.forward(out);
|
in.forward(out);
|
|
@ -0,0 +1,32 @@
|
||||||
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.StreamReadConnection;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class TextReadConnection extends StreamReadConnection implements io.xpipe.core.source.TextReadConnection {
|
||||||
|
|
||||||
|
private BufferedReader bufferedReader;
|
||||||
|
|
||||||
|
public TextReadConnection(TextSource source) {
|
||||||
|
super(source.getStore(), source.getCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws Exception {
|
||||||
|
super.init();
|
||||||
|
bufferedReader = new BufferedReader(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> lines() throws Exception {
|
||||||
|
return bufferedReader.lines();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
bufferedReader.close();
|
||||||
|
}
|
||||||
|
}
|
44
core/src/main/java/io/xpipe/core/impl/TextSource.java
Normal file
44
core/src/main/java/io/xpipe/core/impl/TextSource.java
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import io.xpipe.core.charsetter.Charsettable;
|
||||||
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
|
import io.xpipe.core.charsetter.StreamCharset;
|
||||||
|
import io.xpipe.core.source.TextDataSource;
|
||||||
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TextSource extends TextDataSource<StreamDataStore> implements Charsettable {
|
||||||
|
|
||||||
|
StreamCharset charset;
|
||||||
|
NewLine newLine;
|
||||||
|
|
||||||
|
public TextSource(StreamDataStore store){
|
||||||
|
this(store, StreamCharset.UTF8, NewLine.LF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public TextSource(StreamDataStore store, StreamCharset charset, NewLine newLine) {
|
||||||
|
super(store);
|
||||||
|
this.charset = charset;
|
||||||
|
this.newLine = newLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected io.xpipe.core.source.TextWriteConnection newWriteConnection() {
|
||||||
|
return new TextWriteConnection(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected io.xpipe.core.source.TextWriteConnection newAppendingWriteConnection() {
|
||||||
|
return new PreservingTextWriteConnection(this, newWriteConnection(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected io.xpipe.core.source.TextReadConnection newReadConnection() {
|
||||||
|
return new TextReadConnection(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.StreamWriteConnection;
|
||||||
|
|
||||||
|
public class TextWriteConnection extends StreamWriteConnection implements io.xpipe.core.source.TextWriteConnection {
|
||||||
|
|
||||||
|
private final TextSource source;
|
||||||
|
|
||||||
|
public TextWriteConnection(TextSource source) {
|
||||||
|
super(source.getStore(), source.getCharset());
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeLine(String line) throws Exception {
|
||||||
|
writer.write(line);
|
||||||
|
writer.write(source.getNewLine().getNewLine());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||||
|
import io.xpipe.core.data.node.TupleNode;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
import io.xpipe.core.data.typed.TypedDataStreamParser;
|
||||||
|
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
|
||||||
|
import io.xpipe.core.source.TableReadConnection;
|
||||||
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
import io.xpipe.core.util.JacksonHelper;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class XpbtReadConnection implements TableReadConnection {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws Exception {
|
||||||
|
this.inputStream = store.openBufferedInput();
|
||||||
|
this.inputStream.mark(8192);
|
||||||
|
var header = new BufferedReader(new InputStreamReader(inputStream)).readLine();
|
||||||
|
this.inputStream.reset();
|
||||||
|
if (header == null || header.trim().length() == 0) {
|
||||||
|
empty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerLength = header.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
this.inputStream.skip(headerLength);
|
||||||
|
List<String> names = JacksonHelper.newMapper()
|
||||||
|
.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
|
||||||
|
.readerFor(new TypeReference<List<String>>(){}).readValue(header);
|
||||||
|
TupleType dataType = TupleType.tableType(names);
|
||||||
|
this.dataType = dataType;
|
||||||
|
this.parser = new TypedDataStreamParser(dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TupleType dataType;
|
||||||
|
private final StreamDataStore store;
|
||||||
|
private InputStream inputStream;
|
||||||
|
private TypedDataStreamParser parser;
|
||||||
|
private boolean empty;
|
||||||
|
|
||||||
|
protected XpbtReadConnection(StreamDataStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TupleType getDataType() {
|
||||||
|
return dataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||||
|
if (empty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader = TypedDataStructureNodeReader.of(dataType);
|
||||||
|
AtomicBoolean quit = new AtomicBoolean(false);
|
||||||
|
AtomicReference<Exception> exception = new AtomicReference<>();
|
||||||
|
while (!quit.get()) {
|
||||||
|
var node = parser.parseStructure(inputStream, reader);
|
||||||
|
if (node == null) {
|
||||||
|
quit.set(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!lineAcceptor.accept(node.asTuple())) {
|
||||||
|
quit.set(true);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
quit.set(true);
|
||||||
|
exception.set(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception.get() != null) {
|
||||||
|
throw exception.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
core/src/main/java/io/xpipe/core/impl/XpbtSource.java
Normal file
25
core/src/main/java/io/xpipe/core/impl/XpbtSource.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import io.xpipe.core.source.TableDataSource;
|
||||||
|
import io.xpipe.core.source.TableReadConnection;
|
||||||
|
import io.xpipe.core.source.TableWriteConnection;
|
||||||
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
|
||||||
|
public class XpbtSource extends TableDataSource<StreamDataStore> {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public XpbtSource(StreamDataStore store) {
|
||||||
|
super(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TableWriteConnection newWriteConnection() {
|
||||||
|
return new XpbtWriteConnection(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TableReadConnection newReadConnection() {
|
||||||
|
return new XpbtReadConnection(store);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||||
|
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||||
|
import io.xpipe.core.data.node.TupleNode;
|
||||||
|
import io.xpipe.core.data.type.TupleType;
|
||||||
|
import io.xpipe.core.data.typed.TypedDataStreamWriter;
|
||||||
|
import io.xpipe.core.source.TableWriteConnection;
|
||||||
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
import io.xpipe.core.util.JacksonHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class XpbtWriteConnection implements TableWriteConnection {
|
||||||
|
|
||||||
|
private final StreamDataStore store;
|
||||||
|
private OutputStream outputStream;
|
||||||
|
private TupleType writtenDescriptor;
|
||||||
|
|
||||||
|
public XpbtWriteConnection(StreamDataStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws Exception {
|
||||||
|
outputStream = store.openOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
if (outputStream != null) {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataStructureNodeAcceptor<TupleNode> writeLinesAcceptor() {
|
||||||
|
return t -> {
|
||||||
|
writeDescriptor(t);
|
||||||
|
TypedDataStreamWriter.writeStructure(outputStream, t, writtenDescriptor);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeDescriptor(TupleNode tupleNode) throws IOException {
|
||||||
|
if (writtenDescriptor != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writtenDescriptor = TupleType.tableType(tupleNode.getKeyNames());
|
||||||
|
|
||||||
|
var writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||||
|
JsonFactory f = new JsonFactory();
|
||||||
|
try (JsonGenerator g = f.createGenerator(writer)
|
||||||
|
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
|
||||||
|
.setPrettyPrinter(new DefaultPrettyPrinter())) {
|
||||||
|
JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
|
||||||
|
.writeValue(g, tupleNode.getKeyNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
package io.xpipe.core.source;
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.util.TokenBuffer;
|
import com.fasterxml.jackson.databind.util.TokenBuffer;
|
||||||
|
import io.xpipe.core.impl.TextSource;
|
||||||
|
import io.xpipe.core.impl.XpbtSource;
|
||||||
|
import io.xpipe.core.store.DataFlow;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.util.JacksonHelper;
|
import io.xpipe.core.util.JacksonHelper;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
@ -13,7 +16,7 @@ import java.util.Optional;
|
||||||
/**
|
/**
|
||||||
* Represents a formal description on what exactly makes up the
|
* Represents a formal description on what exactly makes up the
|
||||||
* actual data source and how to access/locate it for a given data store.
|
* actual data source and how to access/locate it for a given data store.
|
||||||
*
|
* <p>
|
||||||
* This instance is only valid in combination with its associated data store instance.
|
* This instance is only valid in combination with its associated data store instance.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@ -21,8 +24,45 @@ import java.util.Optional;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public abstract class DataSource<DS extends DataStore> {
|
public abstract class DataSource<DS extends DataStore> {
|
||||||
|
|
||||||
|
|
||||||
|
public static DataSource<?> createInternalDataSource(DataSourceType t, DataStore store) {
|
||||||
|
try {
|
||||||
|
return switch (t) {
|
||||||
|
case TABLE -> new XpbtSource(store.asNeeded());
|
||||||
|
case STRUCTURE -> null;
|
||||||
|
case TEXT -> new TextSource(store.asNeeded());
|
||||||
|
case RAW -> null;
|
||||||
|
//TODO
|
||||||
|
case COLLECTION -> null;
|
||||||
|
};
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new AssertionError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected DS store;
|
protected DS store;
|
||||||
|
|
||||||
|
|
||||||
|
public void test() throws Exception {
|
||||||
|
store.test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate() throws Exception {
|
||||||
|
if (store == null) {
|
||||||
|
throw new IllegalStateException("Store cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
store.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataFlow getFlow() {
|
||||||
|
if (store == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.getFlow();
|
||||||
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends DataSource<DS>> T copy() {
|
public <T extends DataSource<DS>> T copy() {
|
||||||
|
@ -38,14 +78,6 @@ public abstract class DataSource<DS extends DataStore> {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportsRead() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsWrite() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Casts this instance to the required type without checking whether a cast is possible.
|
* Casts this instance to the required type without checking whether a cast is possible.
|
||||||
*/
|
*/
|
||||||
|
@ -81,6 +113,10 @@ public abstract class DataSource<DS extends DataStore> {
|
||||||
throw new UnsupportedOperationException("Appending write is not supported");
|
throw new UnsupportedOperationException("Appending write is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DataSourceConnection openPrependingWriteConnection() throws Exception {
|
||||||
|
throw new UnsupportedOperationException("Prepending write is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
public DS getStore() {
|
public DS getStore() {
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.core.source;
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import io.xpipe.core.impl.PreservingTableWriteConnection;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -16,10 +17,14 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DataSourceInfo determineInfo() throws Exception {
|
public final DataSourceInfo determineInfo() throws Exception {
|
||||||
|
if (!getFlow().hasInput()) {
|
||||||
|
return new DataSourceInfo.Table(null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
try (var con = openReadConnection()) {
|
try (var con = openReadConnection()) {
|
||||||
var dataType = con.getDataType();
|
var dataType = con.getDataType();
|
||||||
var rowCount = con.getRowCount();
|
var rowCount = con.getRowCount();
|
||||||
return new DataSourceInfo.Table(dataType, rowCount);
|
return new DataSourceInfo.Table(dataType, rowCount.orElse(-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +46,22 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final TableWriteConnection openPrependingWriteConnection() throws Exception {
|
||||||
|
var con = newPrependingWriteConnection();
|
||||||
|
con.init();
|
||||||
|
return con;
|
||||||
|
}
|
||||||
|
|
||||||
protected TableWriteConnection newWriteConnection() {
|
protected TableWriteConnection newWriteConnection() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TableWriteConnection newAppendingWriteConnection() {
|
protected TableWriteConnection newAppendingWriteConnection() {
|
||||||
throw new UnsupportedOperationException();
|
return new PreservingTableWriteConnection(this, newWriteConnection(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TableWriteConnection newPrependingWriteConnection() {
|
||||||
|
return new PreservingTableWriteConnection(this, newWriteConnection(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TableReadConnection newReadConnection() {
|
protected TableReadConnection newReadConnection() {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io.xpipe.core.data.typed.TypedDataStreamWriter;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.OptionalInt;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,8 +26,8 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() throws Exception {
|
public OptionalInt getRowCount() throws Exception {
|
||||||
return 0;
|
return OptionalInt.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,7 +50,9 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
||||||
/**
|
/**
|
||||||
* Returns the amount of rows to be read or -1 if the amount is unknown.
|
* Returns the amount of rows to be read or -1 if the amount is unknown.
|
||||||
*/
|
*/
|
||||||
int getRowCount() throws Exception;
|
default OptionalInt getRowCount() throws Exception {
|
||||||
|
return OptionalInt.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes the table rows until the acceptor returns false.
|
* Consumes the table rows until the acceptor returns false.
|
||||||
|
@ -59,7 +62,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
||||||
/**
|
/**
|
||||||
* Reads multiple rows in bulk.
|
* Reads multiple rows in bulk.
|
||||||
*/
|
*/
|
||||||
default ArrayNode readRows(int maxLines) throws Exception{
|
default ArrayNode readRows(int maxLines) throws Exception {
|
||||||
var list = new ArrayList<DataStructureNode>();
|
var list = new ArrayList<DataStructureNode>();
|
||||||
|
|
||||||
AtomicInteger rowCounter = new AtomicInteger();
|
AtomicInteger rowCounter = new AtomicInteger();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.core.source;
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import io.xpipe.core.impl.PreservingTextWriteConnection;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@ -54,8 +55,26 @@ public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final TextWriteConnection openPrependingWriteConnection() throws Exception {
|
||||||
|
var con = newPrependingWriteConnection();
|
||||||
|
con.init();
|
||||||
|
return con;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract TextWriteConnection newWriteConnection();
|
protected abstract TextWriteConnection newWriteConnection();
|
||||||
protected abstract TextWriteConnection newAppendingWriteConnection();
|
|
||||||
|
protected TextWriteConnection newAppendingWriteConnection() {
|
||||||
|
return new PreservingTextWriteConnection(this, newWriteConnection(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected TextWriteConnection newPrependingWriteConnection() {
|
||||||
|
return new PreservingTextWriteConnection(this, newWriteConnection(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract TextReadConnection newReadConnection();
|
protected abstract TextReadConnection newReadConnection();
|
||||||
}
|
}
|
||||||
|
|
27
core/src/main/java/io/xpipe/core/source/WriteMode.java
Normal file
27
core/src/main/java/io/xpipe/core/source/WriteMode.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public enum WriteMode {
|
||||||
|
@JsonProperty("replace")
|
||||||
|
REPLACE(DataSource::openWriteConnection),
|
||||||
|
@JsonProperty("append")
|
||||||
|
APPEND(DataSource::openAppendingWriteConnection),
|
||||||
|
@JsonProperty("prepend")
|
||||||
|
PREPEND(DataSource::openPrependingWriteConnection);
|
||||||
|
|
||||||
|
public static interface FailableFunction<T, R, E extends Throwable> {
|
||||||
|
R apply(T input) throws E;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final FailableFunction<DataSource<?>, DataSourceConnection, Exception> connectionOpener;
|
||||||
|
|
||||||
|
WriteMode(FailableFunction<DataSource<?>, DataSourceConnection, Exception> connectionOpener) {
|
||||||
|
this.connectionOpener = connectionOpener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSourceConnection open(DataSource<?> source) throws Exception {
|
||||||
|
return connectionOpener.apply(source);
|
||||||
|
}
|
||||||
|
}
|
23
core/src/main/java/io/xpipe/core/store/DataFlow.java
Normal file
23
core/src/main/java/io/xpipe/core/store/DataFlow.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public enum DataFlow {
|
||||||
|
|
||||||
|
@JsonProperty("input")
|
||||||
|
INPUT,
|
||||||
|
@JsonProperty("output")
|
||||||
|
OUTPUT,
|
||||||
|
@JsonProperty("inputOutput")
|
||||||
|
INPUT_OUTPUT,
|
||||||
|
@JsonProperty("transformer")
|
||||||
|
TRANSFORMER;
|
||||||
|
|
||||||
|
public boolean hasInput() {
|
||||||
|
return this == INPUT || this == INPUT_OUTPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOutput() {
|
||||||
|
return this == OUTPUT || this == INPUT_OUTPUT;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,8 +16,8 @@ import java.util.Optional;
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
public interface DataStore {
|
public interface DataStore {
|
||||||
|
|
||||||
default DataStoreFlow getFlow() {
|
default DataFlow getFlow() {
|
||||||
return DataStoreFlow.INOUT;
|
return DataFlow.INPUT_OUTPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,9 +28,6 @@ public interface DataStore {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean canWrite() throws Exception {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether this data store can only be accessed by the current running application.
|
* Indicates whether this data store can only be accessed by the current running application.
|
||||||
|
@ -39,7 +36,7 @@ public interface DataStore {
|
||||||
* @see StdinDataStore
|
* @see StdinDataStore
|
||||||
* @see StdoutDataStore
|
* @see StdoutDataStore
|
||||||
*/
|
*/
|
||||||
default boolean isLocalToApplication() {
|
default boolean isContentExclusivelyAccessible() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +56,9 @@ public interface DataStore {
|
||||||
*
|
*
|
||||||
* @throws Exception if any part of the validation went wrong
|
* @throws Exception if any part of the validation went wrong
|
||||||
*/
|
*/
|
||||||
|
default void test() throws Exception {
|
||||||
|
}
|
||||||
|
|
||||||
default void validate() throws Exception {
|
default void validate() throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,17 +66,6 @@ public interface DataStore {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a display string of this store.
|
|
||||||
*/
|
|
||||||
default String toSummaryString() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
default String queryInformationString() throws Exception {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Casts this instance to the required type without checking whether a cast is possible.
|
* Casts this instance to the required type without checking whether a cast is possible.
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
package io.xpipe.core.store;
|
|
||||||
|
|
||||||
public enum DataStoreFlow {
|
|
||||||
|
|
||||||
INPUT,
|
|
||||||
OUTPUT,
|
|
||||||
INOUT
|
|
||||||
}
|
|
|
@ -37,9 +37,13 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate() throws Exception {
|
public void validate() throws Exception {
|
||||||
if (!machine.exists(file)) {
|
if (machine == null) {
|
||||||
throw new IllegalStateException("File " + file + " could not be found on machine " + machine.toSummaryString());
|
throw new IllegalStateException("Machine is missing");
|
||||||
}
|
}
|
||||||
|
if (file == null) {
|
||||||
|
throw new IllegalStateException("File is missing");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,16 +61,6 @@ public class FileStore implements StreamDataStore, FilenameStore {
|
||||||
return machine.exists(file);
|
return machine.exists(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toSummaryString() {
|
|
||||||
return file + "@" + machine.toSummaryString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean persistent() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFileName() {
|
public String getFileName() {
|
||||||
var split = file.split("[\\\\/]");
|
var split = file.split("[\\\\/]");
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class InMemoryStore implements StreamDataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocalToApplication() {
|
public boolean isContentExclusivelyAccessible() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +47,4 @@ public class InMemoryStore implements StreamDataStore {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toSummaryString() {
|
|
||||||
return "inMemory";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.core.store;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
import io.xpipe.core.charsetter.NewLine;
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
import io.xpipe.core.util.Secret;
|
import io.xpipe.core.util.Secret;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -16,6 +17,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
@JsonTypeName("local")
|
@JsonTypeName("local")
|
||||||
@Value
|
@Value
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
public class LocalStore extends StandardShellStore implements MachineFileStore {
|
public class LocalStore extends StandardShellStore implements MachineFileStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,7 +51,7 @@ public class LocalStore extends StandardShellStore implements MachineFileStore {
|
||||||
var l = type.switchTo(command);
|
var l = type.switchTo(command);
|
||||||
var builder = new ProcessBuilder(l);
|
var builder = new ProcessBuilder(l);
|
||||||
process = builder.start();
|
process = builder.start();
|
||||||
charset = type.getCharset();
|
charset = type.determineCharset(LocalStore.this);
|
||||||
|
|
||||||
var t = new Thread(() -> {
|
var t = new Thread(() -> {
|
||||||
try (var inputStream = createInputStream()){
|
try (var inputStream = createInputStream()){
|
||||||
|
@ -109,11 +111,6 @@ public class LocalStore extends StandardShellStore implements MachineFileStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toSummaryString() {
|
|
||||||
return "localhost";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream openInput(String file) throws Exception {
|
public InputStream openInput(String file) throws Exception {
|
||||||
var p = Path.of(file);
|
var p = Path.of(file);
|
||||||
|
|
|
@ -23,7 +23,7 @@ public final class NamedStore implements DataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate() throws Exception {
|
public void test() throws Exception {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +32,6 @@ public final class NamedStore implements DataStore {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toSummaryString() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <DS extends DataStore> DS asNeeded() {
|
public <DS extends DataStore> DS asNeeded() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|
|
@ -23,6 +23,6 @@ public class OutputStreamStore implements StreamDataStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canOpen() {
|
public boolean canOpen() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
import io.xpipe.core.charsetter.NewLine;
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
import io.xpipe.core.util.Secret;
|
import io.xpipe.core.util.Secret;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -20,10 +21,11 @@ public class ShellTypes {
|
||||||
o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null).trim();
|
o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null).trim();
|
||||||
if (o.equals("PowerShell")) {
|
if (o.equals("PowerShell")) {
|
||||||
return POWERSHELL;
|
return POWERSHELL;
|
||||||
} else
|
} else {
|
||||||
return CMD;
|
return CMD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
|
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
|
||||||
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null);
|
var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null);
|
||||||
|
@ -34,59 +36,36 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonProperty("powershell")
|
public static final StandardShellStore.ShellType POWERSHELL = new PowerShell();
|
||||||
public static final StandardShellStore.ShellType POWERSHELL = new StandardShellStore.ShellType() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> switchTo(List<String> cmd) {
|
public static final StandardShellStore.ShellType CMD = new Cmd();
|
||||||
var l = new ArrayList<>(cmd);
|
|
||||||
l.add(0, "powershell.exe");
|
public static final StandardShellStore.ShellType SH = new Sh();
|
||||||
return l;
|
|
||||||
|
public static StandardShellStore.ShellType getDefault() {
|
||||||
|
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||||
|
return CMD;
|
||||||
|
} else {
|
||||||
|
return SH;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> createFileReadCommand(String file) {
|
|
||||||
return List.of("Get-Content", file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public static StandardShellStore.ShellType[] getWindowsShells() {
|
||||||
public List<String> createFileWriteCommand(String file) {
|
return new StandardShellStore.ShellType[]{
|
||||||
return List.of("Out-File", "-FilePath", file);
|
CMD,
|
||||||
}
|
POWERSHELL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public static StandardShellStore.ShellType[] getLinuxShells() {
|
||||||
public List<String> createFileExistsCommand(String file) {
|
return new StandardShellStore.ShellType[]{SH};
|
||||||
return List.of("Test-Path", "-path", file);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@JsonTypeName("cmd")
|
||||||
public Charset getCharset() {
|
@Value
|
||||||
return StandardCharsets.UTF_16LE;
|
public static class Cmd implements StandardShellStore.ShellType {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NewLine getNewLine() {
|
|
||||||
return NewLine.CRLF;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "powershell";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayName() {
|
|
||||||
return "PowerShell";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getOperatingSystemNameCommand() {
|
|
||||||
return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\"");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@JsonProperty("cmd")
|
|
||||||
public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NewLine getNewLine() {
|
public NewLine getNewLine() {
|
||||||
|
@ -98,6 +77,8 @@ public class ShellTypes {
|
||||||
var l = new ArrayList<>(cmd);
|
var l = new ArrayList<>(cmd);
|
||||||
l.add(0, "cmd.exe");
|
l.add(0, "cmd.exe");
|
||||||
l.add(1, "/c");
|
l.add(1, "/c");
|
||||||
|
l.add(2, "@chcp 65001>nul");
|
||||||
|
l.add(3, "&&");
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,9 +104,8 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Charset getCharset() {
|
public Charset determineCharset(ShellStore store) throws Exception {
|
||||||
// TODO
|
return StandardCharsets.UTF_8;
|
||||||
return StandardCharsets.ISO_8859_1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -142,10 +122,63 @@ public class ShellTypes {
|
||||||
public List<String> getOperatingSystemNameCommand() {
|
public List<String> getOperatingSystemNameCommand() {
|
||||||
return List.of("Get-ComputerInfo");
|
return List.of("Get-ComputerInfo");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
@JsonProperty("sh")
|
@JsonTypeName("powershell")
|
||||||
public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() {
|
@Value
|
||||||
|
public static class PowerShell implements StandardShellStore.ShellType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> switchTo(List<String> cmd) {
|
||||||
|
var l = new ArrayList<>(cmd);
|
||||||
|
l.add(0, "powershell.exe");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> createFileReadCommand(String file) {
|
||||||
|
return List.of("Get-Content", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> createFileWriteCommand(String file) {
|
||||||
|
return List.of("Out-File", "-FilePath", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> createFileExistsCommand(String file) {
|
||||||
|
return List.of("Test-Path", "-path", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Charset determineCharset(ShellStore store) throws Exception {
|
||||||
|
return StandardCharsets.UTF_16LE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NewLine getNewLine() {
|
||||||
|
return NewLine.CRLF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "powershell";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "PowerShell";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getOperatingSystemNameCommand() {
|
||||||
|
return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("sh")
|
||||||
|
@Value
|
||||||
|
public static class Sh implements StandardShellStore.ShellType {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> switchTo(List<String> cmd) {
|
public List<String> switchTo(List<String> cmd) {
|
||||||
|
@ -157,7 +190,7 @@ public class ShellTypes {
|
||||||
var l = new ArrayList<>(cmd);
|
var l = new ArrayList<>(cmd);
|
||||||
l.add(0, "sudo");
|
l.add(0, "sudo");
|
||||||
l.add(1, "-S");
|
l.add(1, "-S");
|
||||||
var pws = new ByteArrayInputStream(pw.getBytes(getCharset()));
|
var pws = new ByteArrayInputStream(pw.getBytes(determineCharset(st)));
|
||||||
return st.prepareCommand(List.of(Secret.createForSecretValue(pw)), l, timeout);
|
return st.prepareCommand(List.of(Secret.createForSecretValue(pw)), l, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +210,7 @@ public class ShellTypes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Charset getCharset() {
|
public Charset determineCharset(ShellStore st) throws Exception {
|
||||||
return StandardCharsets.UTF_8;
|
return StandardCharsets.UTF_8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,32 +233,5 @@ public class ShellTypes {
|
||||||
public List<String> getOperatingSystemNameCommand() {
|
public List<String> getOperatingSystemNameCommand() {
|
||||||
return List.of("uname", "-o");
|
return List.of("uname", "-o");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
public static StandardShellStore.ShellType getDefault() {
|
|
||||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
|
||||||
return CMD;
|
|
||||||
} else {
|
|
||||||
return SH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static StandardShellStore.ShellType[] getWindowsShells() {
|
|
||||||
return new StandardShellStore.ShellType[]{CMD, POWERSHELL};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StandardShellStore.ShellType[] getLinuxShells() {
|
|
||||||
return new StandardShellStore.ShellType[]{SH};
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
ShellTypes(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.core.store;
|
package io.xpipe.core.store;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import io.xpipe.core.charsetter.NewLine;
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
import io.xpipe.core.util.Secret;
|
import io.xpipe.core.util.Secret;
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ import java.util.List;
|
||||||
|
|
||||||
public abstract class StandardShellStore extends ShellStore implements MachineFileStore {
|
public abstract class StandardShellStore extends ShellStore implements MachineFileStore {
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
public static interface ShellType {
|
public static interface ShellType {
|
||||||
|
|
||||||
List<String> switchTo(List<String> cmd);
|
List<String> switchTo(List<String> cmd);
|
||||||
|
@ -25,7 +26,7 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
|
||||||
|
|
||||||
List<String> createFileExistsCommand(String file);
|
List<String> createFileExistsCommand(String file);
|
||||||
|
|
||||||
Charset getCharset();
|
Charset determineCharset(ShellStore store) throws Exception;
|
||||||
|
|
||||||
NewLine getNewLine();
|
NewLine getNewLine();
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import java.io.OutputStream;
|
||||||
public class StdinDataStore implements StreamDataStore {
|
public class StdinDataStore implements StreamDataStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocalToApplication() {
|
public boolean isContentExclusivelyAccessible() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,12 @@ import java.io.OutputStream;
|
||||||
public class StdoutDataStore implements StreamDataStore {
|
public class StdoutDataStore implements StreamDataStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLocalToApplication() {
|
public boolean canOpen() throws Exception {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isContentExclusivelyAccessible() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,13 +35,4 @@ public interface StreamDataStore extends DataStore {
|
||||||
default OutputStream openOutput() throws Exception {
|
default OutputStream openOutput() throws Exception {
|
||||||
throw new UnsupportedOperationException("Can't open store output");
|
throw new UnsupportedOperationException("Can't open store output");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether this store is persistent, i.e. whether the stored data can be read again or not.
|
|
||||||
* The caller has to adapt accordingly based on the persistence property.
|
|
||||||
*/
|
|
||||||
default boolean persistent() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
@ -19,6 +20,7 @@ import io.xpipe.core.dialog.BaseQueryElement;
|
||||||
import io.xpipe.core.dialog.BusyElement;
|
import io.xpipe.core.dialog.BusyElement;
|
||||||
import io.xpipe.core.dialog.ChoiceElement;
|
import io.xpipe.core.dialog.ChoiceElement;
|
||||||
import io.xpipe.core.dialog.HeaderElement;
|
import io.xpipe.core.dialog.HeaderElement;
|
||||||
|
import io.xpipe.core.source.DataSource;
|
||||||
import io.xpipe.core.source.DataSourceInfo;
|
import io.xpipe.core.source.DataSourceInfo;
|
||||||
import io.xpipe.core.source.DataSourceReference;
|
import io.xpipe.core.source.DataSourceReference;
|
||||||
import io.xpipe.core.store.*;
|
import io.xpipe.core.store.*;
|
||||||
|
@ -46,6 +48,10 @@ public class CoreJacksonModule extends SimpleModule {
|
||||||
new NamedType(ArrayType.class),
|
new NamedType(ArrayType.class),
|
||||||
new NamedType(WildcardType.class),
|
new NamedType(WildcardType.class),
|
||||||
|
|
||||||
|
new NamedType(ShellTypes.Cmd.class),
|
||||||
|
new NamedType(ShellTypes.PowerShell.class),
|
||||||
|
new NamedType(ShellTypes.Sh.class),
|
||||||
|
|
||||||
new NamedType(DataSourceInfo.Table.class),
|
new NamedType(DataSourceInfo.Table.class),
|
||||||
new NamedType(DataSourceInfo.Structure.class),
|
new NamedType(DataSourceInfo.Structure.class),
|
||||||
new NamedType(DataSourceInfo.Text.class),
|
new NamedType(DataSourceInfo.Text.class),
|
||||||
|
@ -75,8 +81,33 @@ public class CoreJacksonModule extends SimpleModule {
|
||||||
|
|
||||||
context.addSerializers(_serializers);
|
context.addSerializers(_serializers);
|
||||||
context.addDeserializers(_deserializers);
|
context.addDeserializers(_deserializers);
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Find better way to supply a default serializer for data sources
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
Class.forName("io.xpipe.extension.ExtensionException");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
addSerializer(DataSource.class, new NullSerializer());
|
||||||
|
addDeserializer(DataSource.class, new NullDeserializer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class NullSerializer extends JsonSerializer<Object> {
|
||||||
|
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
|
||||||
|
jgen.writeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NullDeserializer extends JsonDeserializer<DataSource> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class DataSourceReferenceSerializer extends JsonSerializer<DataSourceReference> {
|
public static class DataSourceReferenceSerializer extends JsonSerializer<DataSourceReference> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -9,8 +9,11 @@ open module io.xpipe.core {
|
||||||
exports io.xpipe.core.data.node;
|
exports io.xpipe.core.data.node;
|
||||||
exports io.xpipe.core.data.typed;
|
exports io.xpipe.core.data.typed;
|
||||||
exports io.xpipe.core.dialog;
|
exports io.xpipe.core.dialog;
|
||||||
|
exports io.xpipe.core.impl;
|
||||||
exports io.xpipe.core.charsetter;
|
exports io.xpipe.core.charsetter;
|
||||||
|
|
||||||
|
requires com.fasterxml.jackson.datatype.jsr310;
|
||||||
|
requires com.fasterxml.jackson.module.paramnames;
|
||||||
requires static com.fasterxml.jackson.core;
|
requires static com.fasterxml.jackson.core;
|
||||||
requires static com.fasterxml.jackson.databind;
|
requires static com.fasterxml.jackson.databind;
|
||||||
requires java.net.http;
|
requires java.net.http;
|
||||||
|
|
|
@ -22,16 +22,19 @@ dependencies {
|
||||||
api project(':core')
|
api project(':core')
|
||||||
api project(':beacon')
|
api project(':beacon')
|
||||||
api project(':api')
|
api project(':api')
|
||||||
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
||||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "2.13.0"
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "2.13.0"
|
||||||
|
|
||||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
compileOnly group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
||||||
implementation group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
|
compileOnly 'net.synedra:validatorfx:0.3.1'
|
||||||
implementation 'net.synedra:validatorfx:0.3.1'
|
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||||
implementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
compileOnly 'com.jfoenix:jfoenix:9.0.10'
|
||||||
implementation 'com.jfoenix:jfoenix:9.0.10'
|
compileOnly 'io.xpipe:fxcomps:0.2.2'
|
||||||
implementation 'io.xpipe:fxcomps:0.2.2'
|
compileOnly 'org.controlsfx:controlsfx:11.1.1'
|
||||||
implementation 'org.controlsfx:controlsfx:11.1.1'
|
compileOnly 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
apply from: 'publish.gradle'
|
apply from: 'publish.gradle'
|
||||||
apply from: "$rootDir/deps/publish-base.gradle"
|
apply from: "$rootDir/deps/publish-base.gradle"
|
||||||
|
|
26
extension/src/main/java/io/xpipe/extension/Cache.java
Normal file
26
extension/src/main/java/io/xpipe/extension/Cache.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package io.xpipe.extension;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public interface Cache {
|
||||||
|
|
||||||
|
Cache INSTANCE = ServiceLoader.load(Cache.class).findFirst().orElseThrow();
|
||||||
|
|
||||||
|
public <T> T getValue(String key, Class<?> type, Supplier<T> notPresent);
|
||||||
|
|
||||||
|
public static <T> T get(String key, Class<T> type, Supplier<T> notPresent) {
|
||||||
|
return INSTANCE.getValue(key, type, notPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Optional<T> getIfPresent(String key, Class<T> type) {
|
||||||
|
return Optional.ofNullable(get(key, type, () -> null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void updateValue(String key, T val);
|
||||||
|
|
||||||
|
public static <T> void update(String key, T val) {
|
||||||
|
INSTANCE.updateValue(key, key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,8 +86,8 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
||||||
|
|
||||||
Dialog configDialog(T source, boolean all);
|
Dialog configDialog(T source, boolean all);
|
||||||
|
|
||||||
default boolean isHidden() {
|
default boolean shouldShow(DataSourceType type) {
|
||||||
return false;
|
return type == null || type == getPrimaryType();
|
||||||
}
|
}
|
||||||
|
|
||||||
DataSourceType getPrimaryType();
|
DataSourceType getPrimaryType();
|
||||||
|
|
|
@ -6,22 +6,20 @@ import io.xpipe.core.store.FileStore;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DataSourceProviders {
|
public class DataSourceProviders {
|
||||||
|
|
||||||
private static Set<DataSourceProvider<?>> ALL;
|
private static List<DataSourceProvider<?>> ALL;
|
||||||
|
|
||||||
public static void init(ModuleLayer layer) {
|
public static void init(ModuleLayer layer) {
|
||||||
if (ALL == null) {
|
if (ALL == null) {
|
||||||
ALL = ServiceLoader.load(layer, DataSourceProvider.class)
|
ALL = ServiceLoader.load(layer, DataSourceProvider.class)
|
||||||
.stream()
|
.stream()
|
||||||
.map(p -> (DataSourceProvider<?>) p.get())
|
.map(p -> (DataSourceProvider<?>) p.get())
|
||||||
.collect(Collectors.toSet());
|
.sorted(Comparator.comparing(DataSourceProvider::getId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
ALL.removeIf(p -> {
|
ALL.removeIf(p -> {
|
||||||
try {
|
try {
|
||||||
p.init();
|
p.init();
|
||||||
|
@ -36,7 +34,7 @@ public class DataSourceProviders {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSourceProvider<?> getNativeDataSourceDescriptorForType(DataSourceType t) {
|
public static DataSourceProvider<?> getInternalProviderForType(DataSourceType t) {
|
||||||
try {
|
try {
|
||||||
return switch (t) {
|
return switch (t) {
|
||||||
case TABLE -> DataSourceProviders.byId("xpbt");
|
case TABLE -> DataSourceProviders.byId("xpbt");
|
||||||
|
@ -161,7 +159,7 @@ public class DataSourceProviders {
|
||||||
.findAny();
|
.findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<DataSourceProvider<?>> getAll() {
|
public static List<DataSourceProvider<?>> getAll() {
|
||||||
return ALL;
|
return ALL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,10 +77,6 @@ public interface DataStoreProvider {
|
||||||
|
|
||||||
DataStore defaultStore();
|
DataStore defaultStore();
|
||||||
|
|
||||||
default String display(DataStore store) {
|
|
||||||
return store.toSummaryString();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> getPossibleNames();
|
List<String> getPossibleNames();
|
||||||
|
|
||||||
default String getId() {
|
default String getId() {
|
||||||
|
@ -89,7 +85,7 @@ public interface DataStoreProvider {
|
||||||
|
|
||||||
List<Class<?>> getStoreClasses();
|
List<Class<?>> getStoreClasses();
|
||||||
|
|
||||||
default DataStoreFlow[] getPossibleFlows() {
|
default DataFlow[] getPossibleFlows() {
|
||||||
return new DataStoreFlow[]{DataStoreFlow.INPUT};
|
return new DataFlow[]{DataFlow.INPUT};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,19 @@ import io.xpipe.core.dialog.Dialog;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.*;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DataStoreProviders {
|
public class DataStoreProviders {
|
||||||
|
|
||||||
private static Set<DataStoreProvider> ALL;
|
private static List<DataStoreProvider> ALL;
|
||||||
|
|
||||||
public static void init(ModuleLayer layer) {
|
public static void init(ModuleLayer layer) {
|
||||||
if (ALL == null) {
|
if (ALL == null) {
|
||||||
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
|
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
|
||||||
.map(ServiceLoader.Provider::get).collect(Collectors.toSet());
|
.map(ServiceLoader.Provider::get)
|
||||||
|
.sorted(Comparator.comparing(DataStoreProvider::getId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
ALL.removeIf(p -> {
|
ALL.removeIf(p -> {
|
||||||
try {
|
try {
|
||||||
return !p.init();
|
return !p.init();
|
||||||
|
@ -70,7 +69,7 @@ public class DataStoreProviders {
|
||||||
return (T) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny().orElseThrow();
|
return (T) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny().orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<DataStoreProvider> getAll() {
|
public static List<DataStoreProvider> getAll() {
|
||||||
return ALL;
|
return ALL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package io.xpipe.extension;
|
|
||||||
|
|
||||||
import io.xpipe.core.charsetter.Charsetter;
|
|
||||||
import io.xpipe.core.charsetter.CharsetterContext;
|
|
||||||
import io.xpipe.core.util.JacksonHelper;
|
|
||||||
import io.xpipe.extension.util.ModuleHelper;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
|
|
||||||
public class ExtensionTest {
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setup() throws Exception {
|
|
||||||
var mod = ModuleLayer.boot().modules().stream()
|
|
||||||
.filter(m -> m.getName().contains(".test"))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
var e = ModuleHelper.getEveryoneModule();
|
|
||||||
for (var pkg : mod.getPackages()) {
|
|
||||||
ModuleHelper.exportAndOpen(pkg, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var extMod = ModuleLayer.boot().modules().stream()
|
|
||||||
.filter(m -> m.getName().equals(mod.getName().replace(".test", "")))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
for (var pkg : extMod.getPackages()) {
|
|
||||||
ModuleHelper.exportAndOpen(pkg, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
JacksonHelper.initClassBased();
|
|
||||||
Charsetter.init(CharsetterContext.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void teardown() throws Exception {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
|
import io.xpipe.extension.util.SimpleValidator;
|
||||||
|
import io.xpipe.extension.util.Validator;
|
||||||
import io.xpipe.fxcomps.Comp;
|
import io.xpipe.fxcomps.Comp;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
|
@ -4,23 +4,20 @@ import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public interface I18n {
|
public interface I18n {
|
||||||
|
|
||||||
I18n INSTANCE = ServiceLoader.load(I18n.class).findFirst().orElseThrow();
|
I18n INSTANCE = ServiceLoader.load(I18n.class).findFirst().orElseThrow();
|
||||||
|
|
||||||
public static Supplier<String> resolver(String s, Object... vars) {
|
|
||||||
return () -> get(s, vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObservableValue<String> observable(String s, Object... vars) {
|
public static ObservableValue<String> observable(String s, Object... vars) {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var key = INSTANCE.getKey(s);
|
||||||
return Bindings.createStringBinding(() -> {
|
return Bindings.createStringBinding(() -> {
|
||||||
return get(s, vars);
|
return get(key, vars);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,5 +25,7 @@ public interface I18n {
|
||||||
return INSTANCE.getLocalised(s, vars);
|
return INSTANCE.getLocalised(s, vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getKey(String s);
|
||||||
|
|
||||||
String getLocalised(String s, Object... vars);
|
String getLocalised(String s, Object... vars);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
import io.xpipe.core.source.DataSource;
|
import io.xpipe.core.source.DataSource;
|
||||||
|
import io.xpipe.extension.util.Validator;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
@ -24,10 +25,12 @@ public interface SupportedApplicationProvider {
|
||||||
public static class InstructionsDisplay {
|
public static class InstructionsDisplay {
|
||||||
Region region;
|
Region region;
|
||||||
Runnable onFinish;
|
Runnable onFinish;
|
||||||
|
Validator validator;
|
||||||
|
|
||||||
public InstructionsDisplay(Region region) {
|
public InstructionsDisplay(Region region) {
|
||||||
this.region = region;
|
this.region = region;
|
||||||
onFinish = null;
|
onFinish = null;
|
||||||
|
validator = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,13 @@ public interface Translatable {
|
||||||
|
|
||||||
static <T extends Translatable> StringConverter<T> stringConverter() {
|
static <T extends Translatable> StringConverter<T> stringConverter() {
|
||||||
return new StringConverter<>() {
|
return new StringConverter<>() {
|
||||||
@Override public String toString(T t) {
|
@Override
|
||||||
|
public String toString(T t) {
|
||||||
return t == null ? null : t.toTranslatedString();
|
return t == null ? null : t.toTranslatedString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public T fromString(String string) {
|
@Override
|
||||||
|
public T fromString(String string) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package io.xpipe.extension;
|
|
||||||
|
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
import io.xpipe.fxcomps.Comp;
|
|
||||||
import javafx.beans.property.Property;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public interface XPipeDaemon {
|
|
||||||
|
|
||||||
static XPipeDaemon getInstance() {
|
|
||||||
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DataStore> getNamedStores();
|
|
||||||
|
|
||||||
public Image image(String file);
|
|
||||||
|
|
||||||
Comp<?> streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider);
|
|
||||||
|
|
||||||
Comp<?> namedStoreChooser(ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category);
|
|
||||||
|
|
||||||
Comp<?> namedSourceChooser(ObservableValue<Predicate<DataSource<?>>> filter, Property<? extends DataSource<?>> selected, DataSourceProvider.Category category);
|
|
||||||
|
|
||||||
Comp<?> sourceProviderChooser(Property<DataSourceProvider<?>> provider, DataSourceProvider.Category category);
|
|
||||||
|
|
||||||
Optional<DataStore> getNamedStore(String name);
|
|
||||||
|
|
||||||
Optional<DataSource<?>> getSource(String id);
|
|
||||||
|
|
||||||
Optional<String> getStoreName(DataStore store);
|
|
||||||
|
|
||||||
Optional<String> getSourceId(DataSource<?> source);
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.extension.comp;
|
||||||
|
|
||||||
import io.xpipe.core.charsetter.StreamCharset;
|
import io.xpipe.core.charsetter.StreamCharset;
|
||||||
import io.xpipe.extension.I18n;
|
import io.xpipe.extension.I18n;
|
||||||
|
import io.xpipe.extension.util.CustomComboBoxBuilder;
|
||||||
import io.xpipe.fxcomps.SimpleComp;
|
import io.xpipe.fxcomps.SimpleComp;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
|
|
@ -8,6 +8,13 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
||||||
|
|
||||||
|
public static final ColorScheme LIGHT_MODE = new ColorScheme(
|
||||||
|
Color.valueOf("0033B3"),
|
||||||
|
Color.valueOf("000000"),
|
||||||
|
Color.valueOf("000000"),
|
||||||
|
Color.valueOf("067D17")
|
||||||
|
);
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getRawString();
|
return getRawString();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +25,11 @@ public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
||||||
.collect(Collectors.joining(System.lineSeparator()));
|
.collect(Collectors.joining(System.lineSeparator()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(CodeSnippets.ColorScheme scheme) {
|
public static Builder builder() {
|
||||||
|
return new Builder(LIGHT_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(ColorScheme scheme) {
|
||||||
return new Builder(scheme);
|
return new Builder(scheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +42,11 @@ public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private CodeSnippets.ColorScheme scheme;
|
private ColorScheme scheme;
|
||||||
private List<Line> lines;
|
private List<Line> lines;
|
||||||
private List<Element> currentLine;
|
private List<Element> currentLine;
|
||||||
|
|
||||||
public Builder(CodeSnippets.ColorScheme scheme) {
|
public Builder(ColorScheme scheme) {
|
||||||
this.scheme = scheme;
|
this.scheme = scheme;
|
||||||
lines = new ArrayList<>();
|
lines = new ArrayList<>();
|
||||||
currentLine = new ArrayList<>();
|
currentLine = new ArrayList<>();
|
||||||
|
@ -115,4 +126,9 @@ public record CodeSnippet(List<CodeSnippet.Line> lines) {
|
||||||
|
|
||||||
public static record Line(List<CodeSnippet.Element> elements) {
|
public static record Line(List<CodeSnippet.Element> elements) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static record ColorScheme(
|
||||||
|
Color keyword, Color identifier, Color type, Color string) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package io.xpipe.extension.comp;
|
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
public class CodeSnippets {
|
|
||||||
|
|
||||||
public static final ColorScheme LIGHT_MODE = new ColorScheme(
|
|
||||||
Color.valueOf("0033B3"),
|
|
||||||
Color.valueOf("000000"),
|
|
||||||
Color.valueOf("000000"),
|
|
||||||
Color.valueOf("067D17")
|
|
||||||
);
|
|
||||||
|
|
||||||
public static record ColorScheme(
|
|
||||||
Color keyword, Color identifier, Color type, Color string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.extension.comp;
|
package io.xpipe.extension.comp;
|
||||||
|
|
||||||
import io.xpipe.core.store.DataStoreFlow;
|
import io.xpipe.core.store.DataFlow;
|
||||||
import io.xpipe.extension.I18n;
|
import io.xpipe.extension.I18n;
|
||||||
import io.xpipe.fxcomps.SimpleComp;
|
import io.xpipe.fxcomps.SimpleComp;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
@ -15,15 +15,15 @@ import java.util.LinkedHashMap;
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class DataStoreFlowChoiceComp extends SimpleComp {
|
public class DataStoreFlowChoiceComp extends SimpleComp {
|
||||||
|
|
||||||
Property<DataStoreFlow> selected;
|
Property<DataFlow> selected;
|
||||||
DataStoreFlow[] available;
|
DataFlow[] available;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var map = new LinkedHashMap<DataStoreFlow, ObservableValue<String>>();
|
var map = new LinkedHashMap<DataFlow, ObservableValue<String>>();
|
||||||
map.put(DataStoreFlow.INPUT, I18n.observable("extension.input"));
|
map.put(DataFlow.INPUT, I18n.observable("extension.input"));
|
||||||
map.put(DataStoreFlow.OUTPUT, I18n.observable("extension.output"));
|
map.put(DataFlow.OUTPUT, I18n.observable("extension.output"));
|
||||||
map.put(DataStoreFlow.INOUT, I18n.observable("extension.inout"));
|
map.put(DataFlow.INPUT_OUTPUT, I18n.observable("extension.inout"));
|
||||||
return new ToggleGroupComp<>(selected, map).apply(struc -> {
|
return new ToggleGroupComp<>(selected, map).apply(struc -> {
|
||||||
new FancyTooltipAugment<>("extension.inputDescription").augment(struc.get().getChildren().get(0));
|
new FancyTooltipAugment<>("extension.inputDescription").augment(struc.get().getChildren().get(0));
|
||||||
new FancyTooltipAugment<>("extension.outputDescription").augment(struc.get().getChildren().get(1));
|
new FancyTooltipAugment<>("extension.outputDescription").augment(struc.get().getChildren().get(1));
|
||||||
|
|
|
@ -41,6 +41,7 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
||||||
|
|
||||||
for (var entry : getEntries()) {
|
for (var entry : getEntries()) {
|
||||||
var line = new HBox();
|
var line = new HBox();
|
||||||
|
line.setFillHeight(true);
|
||||||
if (!wrap) {
|
if (!wrap) {
|
||||||
line.prefWidthProperty().bind(flow.widthProperty());
|
line.prefWidthProperty().bind(flow.widthProperty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,13 @@ import io.xpipe.extension.I18n;
|
||||||
import io.xpipe.fxcomps.CompStructure;
|
import io.xpipe.fxcomps.CompStructure;
|
||||||
import io.xpipe.fxcomps.Shortcuts;
|
import io.xpipe.fxcomps.Shortcuts;
|
||||||
import io.xpipe.fxcomps.augment.Augment;
|
import io.xpipe.fxcomps.augment.Augment;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import io.xpipe.fxcomps.util.PlatformThread;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -23,8 +21,8 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
||||||
|
|
||||||
private final ObservableValue<String> text;
|
private final ObservableValue<String> text;
|
||||||
|
|
||||||
public FancyTooltipAugment(Supplier<String> text) {
|
public FancyTooltipAugment(ObservableValue<String> text) {
|
||||||
this.text = new SimpleObjectProperty<>(text.get());
|
this.text = PlatformThread.sync(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FancyTooltipAugment(String key) {
|
public FancyTooltipAugment(String key) {
|
||||||
|
@ -73,8 +71,48 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
||||||
@Override
|
@Override
|
||||||
protected void show() {
|
protected void show() {
|
||||||
Window owner = getOwnerWindow();
|
Window owner = getOwnerWindow();
|
||||||
if (owner.isFocused())
|
if (owner == null || owner.isFocused()) {
|
||||||
super.show();
|
super.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hide() {
|
||||||
|
Window owner = getOwnerWindow();
|
||||||
|
if (owner == null || owner.isFocused()) {
|
||||||
|
super.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show(Node ownerNode, double anchorX, double anchorY) {
|
||||||
|
Window owner = getOwnerWindow();
|
||||||
|
if (owner == null || owner.isFocused()) {
|
||||||
|
super.show(ownerNode, anchorX, anchorY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showOnAnchors(Node ownerNode, double anchorX, double anchorY) {
|
||||||
|
Window owner = getOwnerWindow();
|
||||||
|
if (owner == null || owner.isFocused()) {
|
||||||
|
super.showOnAnchors(ownerNode, anchorX, anchorY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show(Window owner) {
|
||||||
|
if (owner == null || owner.isFocused()) {
|
||||||
|
super.show(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show(Window ownerWindow, double anchorX, double anchorY) {
|
||||||
|
Window owner = getOwnerWindow();
|
||||||
|
if (owner == null || owner.isFocused()) {
|
||||||
|
super.show(ownerWindow, anchorX, anchorY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
package io.xpipe.extension.comp;
|
||||||
|
|
||||||
|
public class MultiVariantComp {
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package io.xpipe.extension.comp;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.WriteMode;
|
||||||
|
import io.xpipe.extension.I18n;
|
||||||
|
import io.xpipe.extension.util.SimpleValidator;
|
||||||
|
import io.xpipe.extension.util.Validatable;
|
||||||
|
import io.xpipe.extension.util.Validator;
|
||||||
|
import io.xpipe.extension.util.Validators;
|
||||||
|
import io.xpipe.fxcomps.SimpleComp;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Value;
|
||||||
|
import net.synedra.validatorfx.Check;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class WriteModeChoiceComp extends SimpleComp implements Validatable {
|
||||||
|
|
||||||
|
Property<WriteMode> selected;
|
||||||
|
WriteMode[] available;
|
||||||
|
Validator validator = new SimpleValidator();
|
||||||
|
Check check;
|
||||||
|
|
||||||
|
public WriteModeChoiceComp(Property<WriteMode> selected, WriteMode[] available) {
|
||||||
|
this.selected = selected;
|
||||||
|
this.available = available;
|
||||||
|
check = Validators.nonNull(validator, I18n.observable("mode"), selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Region createSimple() {
|
||||||
|
var map = new LinkedHashMap<WriteMode, ObservableValue<String>>();
|
||||||
|
map.put(WriteMode.REPLACE, I18n.observable("replace"));
|
||||||
|
map.put(WriteMode.APPEND, I18n.observable("append"));
|
||||||
|
map.put(WriteMode.PREPEND, I18n.observable("prepend"));
|
||||||
|
return new ToggleGroupComp<>(selected, map).apply(struc -> {
|
||||||
|
new FancyTooltipAugment<>("extension.replaceDescription").augment(struc.get().getChildren().get(0));
|
||||||
|
new FancyTooltipAugment<>("extension.appendDescription").augment(struc.get().getChildren().get(1));
|
||||||
|
new FancyTooltipAugment<>("extension.prependDescription").augment(struc.get().getChildren().get(2));
|
||||||
|
}).apply(struc -> check.decorates(struc.get())).createRegion();
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,19 @@ public class ExceptionConverter {
|
||||||
|
|
||||||
public static String convertMessage(Throwable ex) {
|
public static String convertMessage(Throwable ex) {
|
||||||
var msg = ex.getLocalizedMessage();
|
var msg = ex.getLocalizedMessage();
|
||||||
|
if (ex instanceof StackOverflowError) {
|
||||||
|
return I18n.get("extension.stackOverflow", msg);
|
||||||
|
}
|
||||||
|
|
||||||
if (ex instanceof FileNotFoundException) {
|
if (ex instanceof FileNotFoundException) {
|
||||||
return I18n.get("extension.fileNotFound", msg);
|
return I18n.get("extension.fileNotFound", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex instanceof UnsupportedOperationException) {
|
||||||
|
return I18n.get("extension.unsupportedOperation", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (ex instanceof ClassNotFoundException) {
|
if (ex instanceof ClassNotFoundException) {
|
||||||
return I18n.get("extension.classNotFound", msg);
|
return I18n.get("extension.classNotFound", msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -15,7 +15,7 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ChainedValidator implements io.xpipe.extension.Validator {
|
public class ChainedValidator implements Validator {
|
||||||
|
|
||||||
private final List<Validator> validators;
|
private final List<Validator> validators;
|
||||||
private final ReadOnlyObjectWrapper<ValidationResult> validationResultProperty = new ReadOnlyObjectWrapper<>(new ValidationResult());
|
private final ReadOnlyObjectWrapper<ValidationResult> validationResultProperty = new ReadOnlyObjectWrapper<>(new ValidationResult());
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.extension.comp;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.extension.comp.FilterComp;
|
||||||
import io.xpipe.fxcomps.util.SimpleChangeListener;
|
import io.xpipe.fxcomps.util.SimpleChangeListener;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.core.charsetter.NewLine;
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
import io.xpipe.core.charsetter.StreamCharset;
|
import io.xpipe.core.charsetter.StreamCharset;
|
||||||
|
@ -50,7 +50,7 @@ public class DialogHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dialog dataStoreFlowQuery(DataStoreFlow flow, DataStoreFlow[] available) {
|
public static Dialog dataStoreFlowQuery(DataFlow flow, DataFlow[] available) {
|
||||||
return Dialog.choice("flow", o -> o.toString(), true, flow, available);
|
return Dialog.choice("flow", o -> o.toString(), true, flow, available);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package io.xpipe.extension.comp;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.core.charsetter.NewLine;
|
import io.xpipe.core.charsetter.NewLine;
|
||||||
import io.xpipe.core.charsetter.StreamCharset;
|
import io.xpipe.core.charsetter.StreamCharset;
|
||||||
import io.xpipe.core.util.Secret;
|
import io.xpipe.core.util.Secret;
|
||||||
import io.xpipe.extension.I18n;
|
import io.xpipe.extension.I18n;
|
||||||
import io.xpipe.extension.Validator;
|
import io.xpipe.extension.comp.*;
|
||||||
import io.xpipe.extension.Validators;
|
|
||||||
import io.xpipe.fxcomps.Comp;
|
import io.xpipe.fxcomps.Comp;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -42,6 +41,9 @@ public class DynamicOptionsBuilder<T> {
|
||||||
this.wrap = false;
|
this.wrap = false;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
public DynamicOptionsBuilder<T> addTitle(String titleKey) {
|
||||||
|
return addTitle(I18n.observable(titleKey));
|
||||||
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> addTitle(ObservableValue<String> title) {
|
public DynamicOptionsBuilder<T> addTitle(ObservableValue<String> title) {
|
||||||
entries.add(new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
entries.add(new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
|
||||||
|
@ -72,7 +74,7 @@ public class DynamicOptionsBuilder<T> {
|
||||||
for (var e : NewLine.values()) {
|
for (var e : NewLine.values()) {
|
||||||
map.put(e, I18n.observable("extension." + e.getId()));
|
map.put(e, I18n.observable("extension." + e.getId()));
|
||||||
}
|
}
|
||||||
var comp = new ChoiceComp<>(prop,map);
|
var comp = new ChoiceComp<>(prop, map);
|
||||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
|
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
|
||||||
props.add(prop);
|
props.add(prop);
|
||||||
return this;
|
return this;
|
||||||
|
@ -149,6 +151,14 @@ public class DynamicOptionsBuilder<T> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicOptionsBuilder<T> addComp(Comp<?> comp) {
|
||||||
|
return addComp((ObservableValue<String>) null, comp, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicOptionsBuilder<T> addComp(Comp<?> comp, Property<?> prop) {
|
||||||
|
return addComp((ObservableValue<String>) null, comp, prop);
|
||||||
|
}
|
||||||
|
|
||||||
public DynamicOptionsBuilder<T> addComp(String nameKey, Comp<?> comp, Property<?> prop) {
|
public DynamicOptionsBuilder<T> addComp(String nameKey, Comp<?> comp, Property<?> prop) {
|
||||||
return addComp(I18n.observable(nameKey), comp, prop);
|
return addComp(I18n.observable(nameKey), comp, prop);
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -12,7 +12,7 @@ import net.synedra.validatorfx.ValidationResult;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class ExclusiveValidator<T> implements io.xpipe.extension.Validator {
|
public final class ExclusiveValidator<T> implements Validator {
|
||||||
|
|
||||||
private final Map<T, ? extends Validator> validators;
|
private final Map<T, ? extends Validator> validators;
|
||||||
private final ObservableValue<T> obs;
|
private final ObservableValue<T> obs;
|
|
@ -43,8 +43,12 @@ public class ExtensionJacksonModule extends SimpleModule {
|
||||||
var mapper = JacksonHelper.newMapper(ExtensionJacksonModule.class);
|
var mapper = JacksonHelper.newMapper(ExtensionJacksonModule.class);
|
||||||
var tree = (ObjectNode) mapper.readTree(p);
|
var tree = (ObjectNode) mapper.readTree(p);
|
||||||
var type = tree.get("type").textValue();
|
var type = tree.get("type").textValue();
|
||||||
var prov = DataSourceProviders.byId(type);
|
var prov = DataSourceProviders.byName(type);
|
||||||
return mapper.treeToValue(tree, prov.getSourceClass());
|
if (prov.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.treeToValue(tree, prov.get().getSourceClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.api.DataSource;
|
||||||
|
import io.xpipe.api.util.XPipeDaemonController;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.core.store.FileStore;
|
||||||
|
import io.xpipe.extension.DataSourceProviders;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class ExtensionTest {
|
||||||
|
|
||||||
|
|
||||||
|
public static DataStore getResource(String name) {
|
||||||
|
var url = ExtensionTest.class.getClassLoader().getResource(name);
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalArgumentException(String.format("File %s does not exist", name));
|
||||||
|
}
|
||||||
|
var file = url.getFile().substring(1);
|
||||||
|
return FileStore.local(Path.of(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataSource getSource(String type, String file) {
|
||||||
|
return DataSource.create(null, type, getResource(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setup() throws Exception {
|
||||||
|
DataSourceProviders.init(ModuleLayer.boot());
|
||||||
|
XPipeDaemonController.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void teardown() throws Exception {
|
||||||
|
XPipeDaemonController.stop();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,58 +0,0 @@
|
||||||
package io.xpipe.extension.util;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class ModuleHelper {
|
|
||||||
|
|
||||||
public static void exportAndOpen(String modName) {
|
|
||||||
var mod = ModuleLayer.boot().modules().stream()
|
|
||||||
.filter(m -> m.getName().equalsIgnoreCase(modName))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
var e = getEveryoneModule();
|
|
||||||
for (var pkg : mod.getPackages()) {
|
|
||||||
exportAndOpen(pkg, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public static Module getEveryoneModule() {
|
|
||||||
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
|
|
||||||
getDeclaredFields0.setAccessible(true);
|
|
||||||
Field[] fields = (Field[]) getDeclaredFields0.invoke(Module.class, false);
|
|
||||||
Field modifiers = null;
|
|
||||||
for (Field each : fields) {
|
|
||||||
if ("EVERYONE_MODULE".equals(each.getName())) {
|
|
||||||
modifiers = each;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modifiers.setAccessible(true);
|
|
||||||
return (Module) modifiers.get(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public static void exportAndOpen(String pkg, Module mod) {
|
|
||||||
if (mod.isExported(pkg) && mod.isOpen(pkg)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredMethods0", boolean.class);
|
|
||||||
getDeclaredFields0.setAccessible(true);
|
|
||||||
Method[] fields = (Method[]) getDeclaredFields0.invoke(Module.class, false);
|
|
||||||
Method modifiers = null;
|
|
||||||
for (Method each : fields) {
|
|
||||||
if ("implAddExportsOrOpens".equals(each.getName())) {
|
|
||||||
modifiers = each;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modifiers.setAccessible(true);
|
|
||||||
|
|
||||||
var e = getEveryoneModule();
|
|
||||||
modifiers.invoke(mod, pkg, e, false, true);
|
|
||||||
modifiers.invoke(mod, pkg, e, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.ScrollBar;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class PrettyListView<T> extends ListView<T> {
|
||||||
|
|
||||||
|
private final ScrollBar vBar = new ScrollBar();
|
||||||
|
private final ScrollBar hBar = new ScrollBar();
|
||||||
|
|
||||||
|
public PrettyListView() {
|
||||||
|
super();
|
||||||
|
skinProperty().addListener(it -> {
|
||||||
|
// first bind, then add new scrollbars, otherwise the new bars will be found
|
||||||
|
bindScrollBars();
|
||||||
|
getChildren().addAll(vBar, hBar);
|
||||||
|
});
|
||||||
|
|
||||||
|
getStyleClass().add("jfx-list-view");
|
||||||
|
|
||||||
|
vBar.setManaged(false);
|
||||||
|
vBar.setOrientation(Orientation.VERTICAL);
|
||||||
|
vBar.getStyleClass().add("pretty-scroll-bar");
|
||||||
|
// vBar.visibleProperty().bind(vBar.visibleAmountProperty().isNotEqualTo(0));
|
||||||
|
|
||||||
|
hBar.setManaged(false);
|
||||||
|
hBar.setOrientation(Orientation.HORIZONTAL);
|
||||||
|
hBar.getStyleClass().add("pretty-scroll-bar");
|
||||||
|
hBar.visibleProperty().setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindScrollBars() {
|
||||||
|
final Set<Node> nodes = lookupAll("VirtualScrollBar");
|
||||||
|
for (Node node : nodes) {
|
||||||
|
if (node instanceof ScrollBar) {
|
||||||
|
ScrollBar bar = (ScrollBar) node;
|
||||||
|
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
|
||||||
|
bindScrollBars(vBar, bar, true);
|
||||||
|
} else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
|
||||||
|
bindScrollBars(hBar, bar, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindScrollBars(ScrollBar scrollBarA, ScrollBar scrollBarB, boolean bindVisibility) {
|
||||||
|
scrollBarA.valueProperty().bindBidirectional(scrollBarB.valueProperty());
|
||||||
|
scrollBarA.minProperty().bindBidirectional(scrollBarB.minProperty());
|
||||||
|
scrollBarA.maxProperty().bindBidirectional(scrollBarB.maxProperty());
|
||||||
|
scrollBarA.visibleAmountProperty().bindBidirectional(scrollBarB.visibleAmountProperty());
|
||||||
|
scrollBarA.unitIncrementProperty().bindBidirectional(scrollBarB.unitIncrementProperty());
|
||||||
|
scrollBarA.blockIncrementProperty().bindBidirectional(scrollBarB.blockIncrementProperty());
|
||||||
|
if (bindVisibility) {
|
||||||
|
scrollBarA.visibleProperty().bind(scrollBarB.visibleProperty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layoutChildren() {
|
||||||
|
super.layoutChildren();
|
||||||
|
|
||||||
|
Insets insets = getInsets();
|
||||||
|
double w = getWidth();
|
||||||
|
double h = getHeight();
|
||||||
|
final double prefWidth = vBar.prefWidth(-1);
|
||||||
|
vBar.resizeRelocate(w - prefWidth - insets.getRight(), insets.getTop(), prefWidth, h - insets.getTop() - insets.getBottom());
|
||||||
|
|
||||||
|
final double prefHeight = hBar.prefHeight(-1);
|
||||||
|
hBar.resizeRelocate(insets.getLeft(), h - prefHeight - insets.getBottom(), w - insets.getLeft() - insets.getRight(), prefHeight);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Properties {
|
public class PropertiesHelper {
|
||||||
|
|
||||||
public static <T, V> void bindExclusive(Property<V> selected, Map<V, ? extends Property<T>> map, Property<T> toBind) {
|
public static <T, V> void bindExclusive(Property<V> selected, Map<V, ? extends Property<T>> map, Property<T> toBind) {
|
||||||
selected.addListener((c,o,n) -> {
|
selected.addListener((c,o,n) -> {
|
|
@ -1,10 +1,13 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.core.source.DataSource;
|
import io.xpipe.core.source.DataSource;
|
||||||
import io.xpipe.core.source.DataSourceType;
|
import io.xpipe.core.source.DataSourceType;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.FilenameStore;
|
import io.xpipe.core.store.FilenameStore;
|
||||||
import io.xpipe.core.store.StreamDataStore;
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
|
import io.xpipe.extension.DataSourceProviders;
|
||||||
|
import io.xpipe.extension.I18n;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
|
@ -0,0 +1,40 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.data.node.DataStructureNode;
|
||||||
|
import io.xpipe.core.data.node.ValueNode;
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
|
||||||
|
public class TypeConverter {
|
||||||
|
|
||||||
|
public static void tagNullType(ValueNode node, String nullValue) {
|
||||||
|
var string = node.asString();
|
||||||
|
if (string.equals(nullValue)) {
|
||||||
|
node.tag(DataStructureNode.NULL_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tagBooleanType(ValueNode node, String trueValue, String falseValue) {
|
||||||
|
var string = node.asString();
|
||||||
|
if (string.equals(trueValue)) {
|
||||||
|
node.tag(DataStructureNode.BOOLEAN_TRUE);
|
||||||
|
node.tag(DataStructureNode.BOOLEAN_VALUE);
|
||||||
|
}
|
||||||
|
if (string.equals(falseValue)) {
|
||||||
|
node.tag(DataStructureNode.BOOLEAN_FALSE);
|
||||||
|
node.tag(DataStructureNode.BOOLEAN_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tagNumberType(ValueNode node) {
|
||||||
|
var string = node.asString();
|
||||||
|
if (NumberUtils.isCreatable(string)) {
|
||||||
|
node.tag(DataStructureNode.IS_NUMBER);
|
||||||
|
var number = NumberUtils.createNumber(string);
|
||||||
|
if (number instanceof Float || number instanceof Double) {
|
||||||
|
node.tag(DataStructureNode.IS_FLOATING_POINT);
|
||||||
|
} else {
|
||||||
|
node.tag(DataStructureNode.IS_INTEGER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.core.dialog.Dialog;
|
import io.xpipe.core.dialog.Dialog;
|
||||||
import io.xpipe.core.source.DataSource;
|
import io.xpipe.core.source.DataSource;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
|
import io.xpipe.extension.ExtensionException;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
public interface Validatable {
|
||||||
|
|
||||||
|
Validator getValidator();
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
import io.xpipe.extension.I18n;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import net.synedra.validatorfx.Check;
|
import net.synedra.validatorfx.Check;
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.DataSource;
|
||||||
|
import io.xpipe.core.source.DataSourceType;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
|
import io.xpipe.extension.DataStoreProvider;
|
||||||
|
import io.xpipe.fxcomps.Comp;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public interface XPipeDaemon {
|
||||||
|
|
||||||
|
static XPipeDaemon getInstance() {
|
||||||
|
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DataStore> getNamedStores();
|
||||||
|
|
||||||
|
public Image image(String file);
|
||||||
|
|
||||||
|
<T extends Comp<?> & Validatable> T streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider);
|
||||||
|
|
||||||
|
<T extends Comp<?> & Validatable> T namedStoreChooser(
|
||||||
|
ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category
|
||||||
|
);
|
||||||
|
|
||||||
|
Comp<?> namedSourceChooser(
|
||||||
|
ObservableValue<Predicate<DataSource<?>>> filter, Property<? extends DataSource<?>> selected, DataSourceProvider.Category category
|
||||||
|
);
|
||||||
|
|
||||||
|
<T extends Comp<?> & Validatable> T sourceProviderChooser(Property<DataSourceProvider<?>> provider, DataSourceProvider.Category category, DataSourceType filter);
|
||||||
|
|
||||||
|
Optional<DataStore> getNamedStore(String name);
|
||||||
|
|
||||||
|
Optional<DataSource<?>> getSource(String id);
|
||||||
|
|
||||||
|
Optional<String> getStoreName(DataStore store);
|
||||||
|
|
||||||
|
Optional<String> getSourceId(DataSource<?> source);
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.Module;
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
import io.xpipe.extension.SupportedApplicationProvider;
|
import io.xpipe.extension.SupportedApplicationProvider;
|
||||||
import io.xpipe.extension.util.ExtensionJacksonModule;
|
import io.xpipe.extension.util.ExtensionJacksonModule;
|
||||||
|
import io.xpipe.extension.util.XPipeDaemon;
|
||||||
|
|
||||||
open module io.xpipe.extension {
|
open module io.xpipe.extension {
|
||||||
exports io.xpipe.extension;
|
exports io.xpipe.extension;
|
||||||
|
@ -9,14 +10,14 @@ open module io.xpipe.extension {
|
||||||
exports io.xpipe.extension.event;
|
exports io.xpipe.extension.event;
|
||||||
exports io.xpipe.extension.prefs;
|
exports io.xpipe.extension.prefs;
|
||||||
exports io.xpipe.extension.util;
|
exports io.xpipe.extension.util;
|
||||||
exports io.xpipe.extension.test;
|
|
||||||
|
|
||||||
requires transitive io.xpipe.core;
|
requires transitive io.xpipe.core;
|
||||||
requires io.xpipe.beacon;
|
requires io.xpipe.beacon;
|
||||||
requires io.xpipe.api;
|
requires io.xpipe.api;
|
||||||
requires com.fasterxml.jackson.databind;
|
requires com.fasterxml.jackson.databind;
|
||||||
requires static org.junit.jupiter.api;
|
requires static org.junit.jupiter.api;
|
||||||
requires transitive javafx.base;
|
requires static org.apache.commons.lang3;
|
||||||
|
requires static javafx.base;
|
||||||
requires static javafx.graphics;
|
requires static javafx.graphics;
|
||||||
requires static javafx.controls;
|
requires static javafx.controls;
|
||||||
requires static io.xpipe.fxcomps;
|
requires static io.xpipe.fxcomps;
|
||||||
|
@ -38,7 +39,8 @@ open module io.xpipe.extension {
|
||||||
uses io.xpipe.extension.event.EventHandler;
|
uses io.xpipe.extension.event.EventHandler;
|
||||||
uses io.xpipe.extension.prefs.PrefsProvider;
|
uses io.xpipe.extension.prefs.PrefsProvider;
|
||||||
uses io.xpipe.extension.DataStoreProvider;
|
uses io.xpipe.extension.DataStoreProvider;
|
||||||
uses io.xpipe.extension.XPipeDaemon;
|
uses XPipeDaemon;
|
||||||
|
uses io.xpipe.extension.Cache;
|
||||||
|
|
||||||
provides Module with ExtensionJacksonModule;
|
provides Module with ExtensionJacksonModule;
|
||||||
}
|
}
|
|
@ -15,4 +15,10 @@ output=Output
|
||||||
inout=In/Out
|
inout=In/Out
|
||||||
inputDescription=This store only produces input for data sources to read
|
inputDescription=This store only produces input for data sources to read
|
||||||
outputDescription=This store only accepts output from data sources to write
|
outputDescription=This store only accepts output from data sources to write
|
||||||
inoutDescription=This store uses both input and output to essentially create a data transformation
|
inoutDescription=This store uses both input and output to essentially create a data transformation
|
||||||
|
replace=Replace
|
||||||
|
append=Append
|
||||||
|
prepend=Prepend
|
||||||
|
replaceDescription=Replaces all content
|
||||||
|
appendDescription=Appends the new content to the existing content
|
||||||
|
prependDescription=Prepends the new content to the existing content
|
Loading…
Reference in a new issue