diff --git a/api/build.gradle b/api/build.gradle index 3287446d2..69b50f142 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -8,6 +8,9 @@ plugins { apply from: "$rootDir/deps/java.gradle" apply from: "$rootDir/deps/junit.gradle" +System.setProperty('excludeExtensionLibrary', 'true') +apply from: "$rootDir/scripts/extension_test.gradle" + version = file('../misc/version').text group = 'io.xpipe' archivesBaseName = 'xpipe-api' @@ -30,31 +33,6 @@ configurations { 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: "$rootDir/deps/publish-base.gradle" \ No newline at end of file diff --git a/api/src/main/java/io/xpipe/api/connector/XPipeConnection.java b/api/src/main/java/io/xpipe/api/connector/XPipeConnection.java index 5e5cebf3b..caf554533 100644 --- a/api/src/main/java/io/xpipe/api/connector/XPipeConnection.java +++ b/api/src/main/java/io/xpipe/api/connector/XPipeConnection.java @@ -20,7 +20,7 @@ public final class XPipeConnection extends BeaconConnection { con.constructSocket(); var element = reference.getStart(); while (true) { - if (element.requiresExplicitUserInput()) { + if (element != null && element.requiresExplicitUserInput()) { throw new IllegalStateException(); } diff --git a/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java b/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java index 9c1eda10f..408c378bc 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataSourceImpl.java @@ -80,7 +80,7 @@ public abstract class DataSourceImpl implements DataSource { } 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 req = StoreStreamExchange.Request.builder().build(); StoreStreamExchange.Response r = con.performOutputExchange(req, out -> { diff --git a/extension/src/main/java/io/xpipe/extension/test/ExtensionTestConnector.java b/api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java similarity index 73% rename from extension/src/main/java/io/xpipe/extension/test/ExtensionTestConnector.java rename to api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java index 65eae53b1..1875b7cf4 100644 --- a/extension/src/main/java/io/xpipe/extension/test/ExtensionTestConnector.java +++ b/api/src/main/java/io/xpipe/api/util/XPipeDaemonController.java @@ -1,22 +1,14 @@ -package io.xpipe.extension.test; +package io.xpipe.api.util; import io.xpipe.api.connector.XPipeConnection; import io.xpipe.beacon.BeaconClient; 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; public static void start() throws Exception { - DataSourceProviders.init(ModuleLayer.boot()); - JacksonHelper.initClassBased(); - Charsetter.init(CharsetterContext.empty()); - if (BeaconServer.isRunning()) { alreadyStarted = true; return; diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index 7868f1d60..063b86acc 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -1,6 +1,7 @@ module io.xpipe.api { exports io.xpipe.api; exports io.xpipe.api.connector; + exports io.xpipe.api.util; requires transitive io.xpipe.core; requires io.xpipe.beacon; diff --git a/extension/src/main/java/io/xpipe/extension/test/ExtensionTest.java b/api/src/test/java/io/xpipe/api/test/ApiTest.java similarity index 56% rename from extension/src/main/java/io/xpipe/extension/test/ExtensionTest.java rename to api/src/test/java/io/xpipe/api/test/ApiTest.java index c067bd5a1..a7ed1377c 100644 --- a/extension/src/main/java/io/xpipe/extension/test/ExtensionTest.java +++ b/api/src/test/java/io/xpipe/api/test/ApiTest.java @@ -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.BeforeAll; -public class ExtensionTest { +public class ApiTest { @BeforeAll public static void setup() throws Exception { - ExtensionTestConnector.start(); + XPipeDaemonController.start(); } @AfterAll public static void teardown() throws Exception { - ExtensionTestConnector.stop(); + XPipeDaemonController.stop(); } } diff --git a/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java b/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java index 3e9b87bb9..2e4c8f45e 100644 --- a/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java +++ b/api/src/test/java/io/xpipe/api/test/DataTableAccumulatorTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test; import java.util.List; import java.util.OptionalInt; -public class DataTableAccumulatorTest extends DaemonControl { +public class DataTableAccumulatorTest extends ApiTest { @Test public void test() { @@ -21,13 +21,13 @@ public class DataTableAccumulatorTest extends DaemonControl { var acc = DataTableAccumulator.create(type); 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); var table = acc.finish(":test"); Assertions.assertEquals(table.getInfo().getDataType(), TupleType.tableType(List.of("col1", "col2"))); Assertions.assertEquals(table.getInfo().getRowCountIfPresent(), OptionalInt.empty()); - var read = table.read(1).at(0); - Assertions.assertEquals(val, read); + // var read = table.read(1).at(0); + // Assertions.assertEquals(val, read); } } diff --git a/api/src/test/java/io/xpipe/api/test/DataTableTest.java b/api/src/test/java/io/xpipe/api/test/DataTableTest.java index c84c60c1d..c733f20f5 100644 --- a/api/src/test/java/io/xpipe/api/test/DataTableTest.java +++ b/api/src/test/java/io/xpipe/api/test/DataTableTest.java @@ -5,7 +5,7 @@ import io.xpipe.core.source.DataSourceId; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -public class DataTableTest extends DaemonControl { +public class DataTableTest extends ApiTest { @BeforeAll public static void setupStorage() throws Exception { diff --git a/beacon/build.gradle b/beacon/build.gradle index aa72a4f13..a46c48b37 100644 --- a/beacon/build.gradle +++ b/beacon/build.gradle @@ -9,9 +9,6 @@ apply from: "$rootDir/deps/java.gradle" apply from: "$rootDir/deps/lombok.gradle" 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 diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java b/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java index 6292d5d74..1f2c07e67 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java @@ -55,19 +55,19 @@ public class BeaconFormat { private byte[] currentBytes; private int index; - private boolean finished; + private boolean lastBlock; @Override public int read() throws IOException { - if ((currentBytes == null || index == currentBytes.length) && !finished) { + if ((currentBytes == null || index == currentBytes.length) && !lastBlock) { readBlock(); } - if (currentBytes != null && index == currentBytes.length && finished) { + if (currentBytes != null && index == currentBytes.length && lastBlock) { return -1; } - int out = currentBytes[index]; + int out = currentBytes[index] & 0xff; index++; return out; } @@ -83,7 +83,7 @@ public class BeaconFormat { currentBytes = in.readNBytes(lengthInt); index = 0; if (lengthInt < SEGMENT_SIZE) { - finished = true; + lastBlock = true; } } }; diff --git a/core/build.gradle b/core/build.gradle index 4a9089d55..cd5577087 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -6,13 +6,14 @@ plugins { } apply from: "$rootDir/deps/java.gradle" -apply from: "$rootDir/deps/jackson.gradle" apply from: "$rootDir/deps/lombok.gradle" apply from: "$rootDir/deps/junit.gradle" -configurations { - compileOnly.extendsFrom(dep) - testImplementation.extendsFrom(dep) + +dependencies{ + 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 diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java index 8e0aa464e..a4f717325 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamParser.java @@ -55,13 +55,13 @@ public class GenericDataStreamParser { } 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)); cb.onName(name); } private static void parseTuple(InputStream in, GenericDataStreamCallback cb) throws IOException { - var size = in.read(); + var size = DataStructureNodeIO.parseShort(in); cb.onTupleStart(size); for (int i = 0; i < size; i++) { parse(in, cb); @@ -71,7 +71,7 @@ public class GenericDataStreamParser { } private static void parseArray(InputStream in, GenericDataStreamCallback cb) throws IOException { - var size = in.read(); + var size = DataStructureNodeIO.parseShort(in); cb.onArrayStart(size); for (int i = 0; i < size; i++) { parse(in, cb); @@ -81,7 +81,7 @@ public class GenericDataStreamParser { } 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 attributes = DataStructureNodeIO.parseAttributes(in); cb.onValue(data, attributes); diff --git a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java index 06fa840af..40c8c501d 100644 --- a/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java +++ b/core/src/main/java/io/xpipe/core/data/generic/GenericDataStreamWriter.java @@ -4,7 +4,6 @@ import io.xpipe.core.data.node.*; import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; public class GenericDataStreamWriter { @@ -27,16 +26,14 @@ public class GenericDataStreamWriter { private static void writeName(OutputStream out, String s) throws IOException { if (s != null) { - var b = s.getBytes(StandardCharsets.UTF_8); out.write(DataStructureNodeIO.GENERIC_NAME_ID); - out.write(b.length); - out.write(b); + DataStructureNodeIO.writeString(out, s); } } private static void writeTuple(OutputStream out, TupleNode tuple) throws IOException { out.write(DataStructureNodeIO.GENERIC_TUPLE_ID); - out.write(tuple.size()); + DataStructureNodeIO.writeShort(out, tuple.size()); for (int i = 0; i < tuple.size(); i++) { writeName(out, tuple.keyNameAt(i)); write(out, tuple.at(i)); @@ -46,7 +43,7 @@ public class GenericDataStreamWriter { private static void writeArray(OutputStream out, ArrayNode array) throws IOException { out.write(DataStructureNodeIO.GENERIC_ARRAY_ID); - out.write(array.size()); + DataStructureNodeIO.writeShort(out, array.size()); for (int i = 0; i < array.size(); i++) { write(out, array.at(i)); } @@ -55,7 +52,7 @@ public class GenericDataStreamWriter { private static void writeValue(OutputStream out, ValueNode n) throws IOException { out.write(DataStructureNodeIO.GENERIC_VALUE_ID); - out.write(n.getRawData().length); + DataStructureNodeIO.writeShort(out, n.getRawData().length); out.write(n.getRawData()); DataStructureNodeIO.writeAttributes(out, n); } diff --git a/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java b/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java index 279c66eb1..9662ba93b 100644 --- a/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ArrayNode.java @@ -31,12 +31,17 @@ public abstract class ArrayNode extends DataStructureNode { if (!(o instanceof ArrayNode that)) { 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 public int hashCode() { - return Objects.hash(getNodes()); + return Objects.hash(getNodes(), getMetaAttributes()); } @Override @@ -52,7 +57,7 @@ public abstract class ArrayNode extends DataStructureNode { @Override public final String toString(int indent) { var content = getNodes().stream().map(n -> n.toString(indent)).collect(Collectors.joining(", ")); - return "[" + content + "]"; + return "[" + content + "] " + metaToString(); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java b/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java index 45b55df97..c049a46c2 100644 --- a/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java @@ -4,6 +4,7 @@ import io.xpipe.core.data.type.DataType; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class DataStructureNode implements Iterable { @@ -11,9 +12,15 @@ public abstract class DataStructureNode implements Iterable { public static final Integer KEY_TABLE_NAME = 1; public static final Integer KEY_ROW_NAME = 2; public static final Integer BOOLEAN_TRUE = 3; - public static final Integer BOOLEAN_FALSE = 4; - public static final Integer INTEGER_VALUE = 5; - public static final Integer TEXT = 5; + public static final Integer BOOLEAN_VALUE = 4; + public static final Integer BOOLEAN_FALSE = 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 metaAttributes; @@ -109,6 +116,15 @@ public abstract class DataStructureNode implements Iterable { 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 boolean isTuple() { diff --git a/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java b/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java index 1776fa90d..06680c2b3 100644 --- a/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java +++ b/core/src/main/java/io/xpipe/core/data/node/DataStructureNodeIO.java @@ -3,6 +3,8 @@ package io.xpipe.core.data.node; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -20,43 +22,61 @@ public class DataStructureNodeIO { public static final int TYPED_ARRAY_ID = 7; 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 { if (s != null) { var b = s.getBytes(StandardCharsets.UTF_8); - out.write(b.length); + DataStructureNodeIO.writeShort(out, b.length); 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 { - var nameLength = in.read(); + var nameLength = parseShort(in); var name = new String(in.readNBytes(nameLength), StandardCharsets.UTF_8); return name; } public static Map parseAttributes(InputStream in) throws IOException { - var attributesLength = in.read(); + var attributesLength = parseShort(in); if (attributesLength == 0) { return null; } var map = new HashMap(); for (int i = 0; i < attributesLength; i++) { - var key = in.read(); + var key = parseShort(in); var value = parseString(in); - map.put(key, value); + map.put((int) key, value); } return map; } public static void writeAttributes(OutputStream out, DataStructureNode s) throws IOException { if (s.getMetaAttributes() != null) { - out.write(s.getMetaAttributes().size()); + writeShort(out, s.getMetaAttributes().size()); for (Map.Entry entry : s.getMetaAttributes().entrySet()) { Integer integer = entry.getKey(); var value = entry.getValue().getBytes(StandardCharsets.UTF_8); - out.write(integer); - out.write(value.length); + writeShort(out, integer); + writeShort(out, value.length); out.write(value); } } else { diff --git a/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java index 5b4a1a1a9..001d47c44 100644 --- a/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleArrayNode.java @@ -1,7 +1,6 @@ package io.xpipe.core.data.node; -import lombok.EqualsAndHashCode; -import lombok.Value; +import lombok.AllArgsConstructor; import java.util.Collections; import java.util.Iterator; @@ -10,8 +9,9 @@ import java.util.Spliterator; import java.util.function.Consumer; import java.util.stream.Stream; -@Value -@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor + + public class SimpleArrayNode extends ArrayNode { List nodes; @@ -79,4 +79,6 @@ public class SimpleArrayNode extends ArrayNode { nodes.remove(index); return this; } + + } diff --git a/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java index e6225692f..4a336c71e 100644 --- a/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleImmutableValueNode.java @@ -27,7 +27,8 @@ public class SimpleImmutableValueNode extends ValueNode { @Override public String toString(int indent) { - return (hasMetaAttribute(TEXT) ? "\"" : "") + asString() + (hasMetaAttribute(TEXT) ? "\"" : "") + " (I)"; + var string = getRawData().length == 0 && !hasMetaAttribute(TEXT) ? "" : new String(getRawData(), StandardCharsets.UTF_8); + return (hasMetaAttribute(TEXT) ? "\"" : "") + string + (hasMetaAttribute(TEXT) ? "\"" : "") + " " + metaToString(); } @Override diff --git a/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java b/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java index 67ac937de..738ce9f6d 100644 --- a/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/SimpleTupleNode.java @@ -18,6 +18,7 @@ public class SimpleTupleNode extends TupleNode { this.names = names; this.nodes = nodes; } + @Override public DataStructureNode set(int index, DataStructureNode node) { nodes.set(index, node); @@ -26,7 +27,8 @@ public class SimpleTupleNode extends TupleNode { @Override 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 @@ -46,7 +48,7 @@ public class SimpleTupleNode extends TupleNode { @Override public DataStructureNode forKey(String name) { - var index = names.indexOf(name); + var index = names != null ? names.indexOf(name) : -1; if (index == -1) { throw new IllegalArgumentException("Key " + name + " not found"); } @@ -56,7 +58,7 @@ public class SimpleTupleNode extends TupleNode { @Override public Optional forKeyIfPresent(String name) { - if (!names.contains(name)) { + if (names == null || !names.contains(name)) { return Optional.empty(); } @@ -77,6 +79,10 @@ public class SimpleTupleNode extends TupleNode { } public String keyNameAt(int index) { + if (names == null) { + return null; + } + return names.get(index); } @@ -84,7 +90,7 @@ public class SimpleTupleNode extends TupleNode { public List getKeyValuePairs() { var l = new ArrayList(size()); 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; } diff --git a/core/src/main/java/io/xpipe/core/data/node/TupleNode.java b/core/src/main/java/io/xpipe/core/data/node/TupleNode.java index e57d59e3e..df3919576 100644 --- a/core/src/main/java/io/xpipe/core/data/node/TupleNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/TupleNode.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; + public abstract class TupleNode extends DataStructureNode { public static Builder builder() { @@ -19,7 +20,7 @@ public abstract class TupleNode extends DataStructureNode { return new SimpleTupleNode(null, nodes); } - public static TupleNode of(List names, List nodes) { + public static TupleNode of(List names, List nodes) { if (names == 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"); } - return new SimpleTupleNode(names, nodes); + return new SimpleTupleNode(names, (List) nodes); } @Override @@ -59,12 +60,16 @@ public abstract class TupleNode extends DataStructureNode { public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof TupleNode that)) return false; - return getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes()); + var toReturn = getKeyNames().equals(that.getKeyNames()) && getNodes().equals(that.getNodes()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes()); + if (toReturn == false) { + throw new AssertionError(); + } + return toReturn; } @Override public int hashCode() { - return Objects.hash(getKeyNames(), getNodes()); + return Objects.hash(getKeyNames(), getNodes(), getMetaAttributes()); } public static class Builder { diff --git a/core/src/main/java/io/xpipe/core/data/node/ValueNode.java b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java index f6e1f9557..01fb0f19b 100644 --- a/core/src/main/java/io/xpipe/core/data/node/ValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java @@ -21,16 +21,20 @@ public abstract class ValueNode extends DataStructureNode { if (!(o instanceof ValueNode that)) { 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 public int hashCode() { - return Arrays.hashCode(getRawData()); + return Arrays.hashCode(getRawData()) + Objects.hash(getMetaAttributes()); } 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) { @@ -38,6 +42,10 @@ public abstract class ValueNode extends DataStructureNode { } public static ValueNode of(Object o) { + if (o == null) { + return nullValue(); + } + return of(o.toString().getBytes(StandardCharsets.UTF_8)); } diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java index 87e3010c0..99da0e492 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamParser.java @@ -120,7 +120,7 @@ public class TypedDataStreamParser { } private void parseTypedArray(InputStream in, TypedDataStreamCallback cb, ArrayType type) throws IOException { - var size = in.read(); + var size = DataStructureNodeIO.parseShort(in); cb.onArrayBegin(size); for (int i = 0; i < size; i++) { parse(in, cb, type.getSharedType()); @@ -129,7 +129,7 @@ public class TypedDataStreamParser { } private void parseValue(InputStream in, TypedDataStreamCallback cb) throws IOException { - var size = in.read(); + var size = DataStructureNodeIO.parseShort(in); var data = in.readNBytes(size); cb.onValue(data); } diff --git a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java index c749e6242..b67117314 100644 --- a/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java +++ b/core/src/main/java/io/xpipe/core/data/typed/TypedDataStreamWriter.java @@ -32,7 +32,7 @@ public class TypedDataStreamWriter { private static void writeValue(OutputStream out, ValueNode n) throws IOException { out.write(DataStructureNodeIO.TYPED_VALUE_ID); - out.write(n.getRawData().length); + DataStructureNodeIO.writeShort(out, n.getRawData().length); out.write(n.getRawData()); } @@ -49,7 +49,7 @@ public class TypedDataStreamWriter { private static void writeArray(OutputStream out, ArrayNode array, ArrayType type) throws IOException { out.write(DataStructureNodeIO.TYPED_ARRAY_ID); - out.write(array.size()); + DataStructureNodeIO.writeShort(out, array.size()); for (int i = 0; i < array.size(); i++) { write(out, array.at(i), type.getSharedType()); } diff --git a/core/src/main/java/io/xpipe/core/dialog/Dialog.java b/core/src/main/java/io/xpipe/core/dialog/Dialog.java index 8af3b381a..61c5862e1 100644 --- a/core/src/main/java/io/xpipe/core/dialog/Dialog.java +++ b/core/src/main/java/io/xpipe/core/dialog/Dialog.java @@ -335,6 +335,10 @@ public abstract class Dialog { @Override public DialogElement start() throws Exception { active = check.get() ? null : d; + if (active == null) { + complete(); + } + return active != null ? active.start() : null; } @@ -427,6 +431,9 @@ public abstract class Dialog { private Supplier evaluation; private final List> 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 Dialog evaluateTo(Dialog d) { diff --git a/extension/src/main/java/io/xpipe/extension/util/AppendingTableWriteConnection.java b/core/src/main/java/io/xpipe/core/impl/PreservingTableWriteConnection.java similarity index 56% rename from extension/src/main/java/io/xpipe/extension/util/AppendingTableWriteConnection.java rename to core/src/main/java/io/xpipe/core/impl/PreservingTableWriteConnection.java index 0ecee468c..286c4a4d6 100644 --- a/extension/src/main/java/io/xpipe/extension/util/AppendingTableWriteConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/PreservingTableWriteConnection.java @@ -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.TupleNode; @@ -7,11 +7,12 @@ import io.xpipe.core.source.DataSourceConnection; import io.xpipe.core.source.DataSourceType; 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 diff --git a/extension/src/main/java/io/xpipe/extension/util/AppendingTextWriteConnection.java b/core/src/main/java/io/xpipe/core/impl/PreservingTextWriteConnection.java similarity index 59% rename from extension/src/main/java/io/xpipe/extension/util/AppendingTextWriteConnection.java rename to core/src/main/java/io/xpipe/core/impl/PreservingTextWriteConnection.java index 20a36a2cd..976710afa 100644 --- a/extension/src/main/java/io/xpipe/extension/util/AppendingTextWriteConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/PreservingTextWriteConnection.java @@ -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.DataSourceConnection; import io.xpipe.core.source.DataSourceType; import io.xpipe.core.source.TextWriteConnection; -public class AppendingTextWriteConnection extends AppendingWriteConnection implements TextWriteConnection { +public class PreservingTextWriteConnection extends PreservingWriteConnection implements TextWriteConnection { - public AppendingTextWriteConnection( - DataSource source, DataSourceConnection connection + public PreservingTextWriteConnection( + DataSource source, DataSourceConnection connection, + boolean append ) { - super(DataSourceType.TEXT, source, connection); + super(DataSourceType.TEXT, source, append, connection); } @Override diff --git a/extension/src/main/java/io/xpipe/extension/util/AppendingWriteConnection.java b/core/src/main/java/io/xpipe/core/impl/PreservingWriteConnection.java similarity index 73% rename from extension/src/main/java/io/xpipe/extension/util/AppendingWriteConnection.java rename to core/src/main/java/io/xpipe/core/impl/PreservingWriteConnection.java index 5fa6b8c87..ff581de78 100644 --- a/extension/src/main/java/io/xpipe/extension/util/AppendingWriteConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/PreservingWriteConnection.java @@ -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.DataSourceConnection; import io.xpipe.core.source.DataSourceType; import io.xpipe.core.store.FileStore; -import io.xpipe.extension.DataSourceProviders; import java.nio.file.Files; -public class AppendingWriteConnection implements DataSourceConnection { +public class PreservingWriteConnection implements DataSourceConnection { private final DataSourceType type; private final DataSource source; + private final boolean append ; 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.source = source; + this.append = append; this.connection = connection; } public void init() throws Exception { var temp = Files.createTempFile(null, null); var nativeStore = FileStore.local(temp); - var nativeSource = DataSourceProviders.getNativeDataSourceDescriptorForType(type).createDefaultSource(nativeStore); + var nativeSource = DataSource.createInternalDataSource(type, nativeStore); if (source.getStore().canOpen()) { try (var in = source.openReadConnection(); var out = nativeSource.openWriteConnection()) { in.forward(out); diff --git a/core/src/main/java/io/xpipe/core/impl/TextReadConnection.java b/core/src/main/java/io/xpipe/core/impl/TextReadConnection.java new file mode 100644 index 000000000..e8bcadae7 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/TextReadConnection.java @@ -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 lines() throws Exception { + return bufferedReader.lines(); + } + + + @Override + public void close() throws Exception { + bufferedReader.close(); + } +} diff --git a/core/src/main/java/io/xpipe/core/impl/TextSource.java b/core/src/main/java/io/xpipe/core/impl/TextSource.java new file mode 100644 index 000000000..dcdbbf883 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/TextSource.java @@ -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 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); + } +} diff --git a/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java b/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java new file mode 100644 index 000000000..eab77a6b8 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java @@ -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()); + } +} diff --git a/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java b/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java new file mode 100644 index 000000000..0d4063227 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java @@ -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 names = JacksonHelper.newMapper() + .disable(JsonParser.Feature.AUTO_CLOSE_SOURCE) + .readerFor(new TypeReference>(){}).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 lineAcceptor) throws Exception { + if (empty) { + return; + } + + var reader = TypedDataStructureNodeReader.of(dataType); + AtomicBoolean quit = new AtomicBoolean(false); + AtomicReference 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(); + } + } +} diff --git a/core/src/main/java/io/xpipe/core/impl/XpbtSource.java b/core/src/main/java/io/xpipe/core/impl/XpbtSource.java new file mode 100644 index 000000000..cf4a0c112 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/XpbtSource.java @@ -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 { + + @JsonCreator + public XpbtSource(StreamDataStore store) { + super(store); + } + + @Override + protected TableWriteConnection newWriteConnection() { + return new XpbtWriteConnection(store); + } + + @Override + protected TableReadConnection newReadConnection() { + return new XpbtReadConnection(store); + } +} diff --git a/core/src/main/java/io/xpipe/core/impl/XpbtWriteConnection.java b/core/src/main/java/io/xpipe/core/impl/XpbtWriteConnection.java new file mode 100644 index 000000000..24e7a7b18 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/impl/XpbtWriteConnection.java @@ -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 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()); + } + } +} diff --git a/core/src/main/java/io/xpipe/core/source/DataSource.java b/core/src/main/java/io/xpipe/core/source/DataSource.java index 3cb492a0d..7c07df20e 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSource.java +++ b/core/src/main/java/io/xpipe/core/source/DataSource.java @@ -1,6 +1,9 @@ package io.xpipe.core.source; 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.util.JacksonHelper; import lombok.AllArgsConstructor; @@ -13,7 +16,7 @@ import java.util.Optional; /** * Represents a formal description on what exactly makes up the * actual data source and how to access/locate it for a given data store. - * + *

* This instance is only valid in combination with its associated data store instance. */ @Data @@ -21,8 +24,45 @@ import java.util.Optional; @AllArgsConstructor public abstract class DataSource { + + 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; + + 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 @SuppressWarnings("unchecked") public > T copy() { @@ -38,14 +78,6 @@ public abstract class DataSource { 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. */ @@ -81,6 +113,10 @@ public abstract class DataSource { throw new UnsupportedOperationException("Appending write is not supported"); } + public DataSourceConnection openPrependingWriteConnection() throws Exception { + throw new UnsupportedOperationException("Prepending write is not supported"); + } + public DS getStore() { return store; } diff --git a/core/src/main/java/io/xpipe/core/source/TableDataSource.java b/core/src/main/java/io/xpipe/core/source/TableDataSource.java index 8c221edee..caa8881ee 100644 --- a/core/src/main/java/io/xpipe/core/source/TableDataSource.java +++ b/core/src/main/java/io/xpipe/core/source/TableDataSource.java @@ -1,5 +1,6 @@ package io.xpipe.core.source; +import io.xpipe.core.impl.PreservingTableWriteConnection; import io.xpipe.core.store.DataStore; import lombok.Data; import lombok.EqualsAndHashCode; @@ -16,10 +17,14 @@ public abstract class TableDataSource extends DataSource extends DataSource(); AtomicInteger rowCounter = new AtomicInteger(); diff --git a/core/src/main/java/io/xpipe/core/source/TextDataSource.java b/core/src/main/java/io/xpipe/core/source/TextDataSource.java index 868890dd6..f21149848 100644 --- a/core/src/main/java/io/xpipe/core/source/TextDataSource.java +++ b/core/src/main/java/io/xpipe/core/source/TextDataSource.java @@ -1,5 +1,6 @@ package io.xpipe.core.source; +import io.xpipe.core.impl.PreservingTextWriteConnection; import io.xpipe.core.store.DataStore; import lombok.NoArgsConstructor; @@ -54,8 +55,26 @@ public abstract class TextDataSource extends DataSource { + R apply(T input) throws E; + + } + + private final FailableFunction, DataSourceConnection, Exception> connectionOpener; + + WriteMode(FailableFunction, DataSourceConnection, Exception> connectionOpener) { + this.connectionOpener = connectionOpener; + } + + public DataSourceConnection open(DataSource source) throws Exception { + return connectionOpener.apply(source); + } +} diff --git a/core/src/main/java/io/xpipe/core/store/DataFlow.java b/core/src/main/java/io/xpipe/core/store/DataFlow.java new file mode 100644 index 000000000..e62c6dbc2 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/DataFlow.java @@ -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; + } +} diff --git a/core/src/main/java/io/xpipe/core/store/DataStore.java b/core/src/main/java/io/xpipe/core/store/DataStore.java index 451565507..a11a6cd21 100644 --- a/core/src/main/java/io/xpipe/core/store/DataStore.java +++ b/core/src/main/java/io/xpipe/core/store/DataStore.java @@ -16,8 +16,8 @@ import java.util.Optional; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface DataStore { - default DataStoreFlow getFlow() { - return DataStoreFlow.INOUT; + default DataFlow getFlow() { + return DataFlow.INPUT_OUTPUT; } /** @@ -28,9 +28,6 @@ public interface DataStore { return true; } - default boolean canWrite() throws Exception { - return true; - } /** * Indicates whether this data store can only be accessed by the current running application. @@ -39,7 +36,7 @@ public interface DataStore { * @see StdinDataStore * @see StdoutDataStore */ - default boolean isLocalToApplication() { + default boolean isContentExclusivelyAccessible() { return false; } @@ -59,6 +56,9 @@ public interface DataStore { * * @throws Exception if any part of the validation went wrong */ + default void test() throws Exception { + } + default void validate() throws Exception { } @@ -66,17 +66,6 @@ public interface DataStore { 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. diff --git a/core/src/main/java/io/xpipe/core/store/DataStoreFlow.java b/core/src/main/java/io/xpipe/core/store/DataStoreFlow.java deleted file mode 100644 index 525c630f6..000000000 --- a/core/src/main/java/io/xpipe/core/store/DataStoreFlow.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.xpipe.core.store; - -public enum DataStoreFlow { - - INPUT, - OUTPUT, - INOUT -} diff --git a/core/src/main/java/io/xpipe/core/store/FileStore.java b/core/src/main/java/io/xpipe/core/store/FileStore.java index 996254f8b..1e4cf3656 100644 --- a/core/src/main/java/io/xpipe/core/store/FileStore.java +++ b/core/src/main/java/io/xpipe/core/store/FileStore.java @@ -37,9 +37,13 @@ public class FileStore implements StreamDataStore, FilenameStore { @Override public void validate() throws Exception { - if (!machine.exists(file)) { - throw new IllegalStateException("File " + file + " could not be found on machine " + machine.toSummaryString()); + if (machine == null) { + throw new IllegalStateException("Machine is missing"); } + if (file == null) { + throw new IllegalStateException("File is missing"); + } + } @Override @@ -57,16 +61,6 @@ public class FileStore implements StreamDataStore, FilenameStore { return machine.exists(file); } - @Override - public String toSummaryString() { - return file + "@" + machine.toSummaryString(); - } - - @Override - public boolean persistent() { - return true; - } - @Override public String getFileName() { var split = file.split("[\\\\/]"); diff --git a/core/src/main/java/io/xpipe/core/store/InMemoryStore.java b/core/src/main/java/io/xpipe/core/store/InMemoryStore.java index 21d86f52e..b2a4f3e35 100644 --- a/core/src/main/java/io/xpipe/core/store/InMemoryStore.java +++ b/core/src/main/java/io/xpipe/core/store/InMemoryStore.java @@ -27,7 +27,7 @@ public class InMemoryStore implements StreamDataStore { } @Override - public boolean isLocalToApplication() { + public boolean isContentExclusivelyAccessible() { return true; } @@ -47,8 +47,4 @@ public class InMemoryStore implements StreamDataStore { }; } - @Override - public String toSummaryString() { - return "inMemory"; - } } diff --git a/core/src/main/java/io/xpipe/core/store/LocalStore.java b/core/src/main/java/io/xpipe/core/store/LocalStore.java index bbdb25d4e..9107be95d 100644 --- a/core/src/main/java/io/xpipe/core/store/LocalStore.java +++ b/core/src/main/java/io/xpipe/core/store/LocalStore.java @@ -3,6 +3,7 @@ package io.xpipe.core.store; import com.fasterxml.jackson.annotation.JsonTypeName; import io.xpipe.core.charsetter.NewLine; import io.xpipe.core.util.Secret; +import lombok.EqualsAndHashCode; import lombok.Value; import java.io.*; @@ -16,6 +17,7 @@ import java.util.stream.Collectors; @JsonTypeName("local") @Value +@EqualsAndHashCode(callSuper = false) public class LocalStore extends StandardShellStore implements MachineFileStore { @Override @@ -49,7 +51,7 @@ public class LocalStore extends StandardShellStore implements MachineFileStore { var l = type.switchTo(command); var builder = new ProcessBuilder(l); process = builder.start(); - charset = type.getCharset(); + charset = type.determineCharset(LocalStore.this); var t = new Thread(() -> { try (var inputStream = createInputStream()){ @@ -109,11 +111,6 @@ public class LocalStore extends StandardShellStore implements MachineFileStore { } - @Override - public String toSummaryString() { - return "localhost"; - } - @Override public InputStream openInput(String file) throws Exception { var p = Path.of(file); diff --git a/core/src/main/java/io/xpipe/core/store/NamedStore.java b/core/src/main/java/io/xpipe/core/store/NamedStore.java index e103b0fe3..ccfc03b7f 100644 --- a/core/src/main/java/io/xpipe/core/store/NamedStore.java +++ b/core/src/main/java/io/xpipe/core/store/NamedStore.java @@ -23,7 +23,7 @@ public final class NamedStore implements DataStore { } @Override - public void validate() throws Exception { + public void test() throws Exception { throw new UnsupportedOperationException(); } @@ -32,11 +32,6 @@ public final class NamedStore implements DataStore { throw new UnsupportedOperationException(); } - @Override - public String toSummaryString() { - throw new UnsupportedOperationException(); - } - @Override public DS asNeeded() { throw new UnsupportedOperationException(); diff --git a/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java b/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java index 3dc094dec..312f12a47 100644 --- a/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java +++ b/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java @@ -23,6 +23,6 @@ public class OutputStreamStore implements StreamDataStore { @Override public boolean canOpen() { - return true; + return false; } } diff --git a/core/src/main/java/io/xpipe/core/store/ShellTypes.java b/core/src/main/java/io/xpipe/core/store/ShellTypes.java index 4f9b87a85..c474aa5fc 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellTypes.java +++ b/core/src/main/java/io/xpipe/core/store/ShellTypes.java @@ -1,8 +1,9 @@ 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.util.Secret; +import lombok.Value; import java.io.ByteArrayInputStream; 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(); if (o.equals("PowerShell")) { return POWERSHELL; - } else + } else { return CMD; } } + } public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception { 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 StandardShellStore.ShellType() { + public static final StandardShellStore.ShellType POWERSHELL = new PowerShell(); - @Override - public List switchTo(List cmd) { - var l = new ArrayList<>(cmd); - l.add(0, "powershell.exe"); - return l; + + public static final StandardShellStore.ShellType CMD = new Cmd(); + + public static final StandardShellStore.ShellType SH = new Sh(); + + public static StandardShellStore.ShellType getDefault() { + if (System.getProperty("os.name").startsWith("Windows")) { + return CMD; + } else { + return SH; } + } - @Override - public List createFileReadCommand(String file) { - return List.of("Get-Content", file); - } - @Override - public List createFileWriteCommand(String file) { - return List.of("Out-File", "-FilePath", file); - } + public static StandardShellStore.ShellType[] getWindowsShells() { + return new StandardShellStore.ShellType[]{ + CMD, + POWERSHELL + }; + } - @Override - public List createFileExistsCommand(String file) { - return List.of("Test-Path", "-path", file); - } + public static StandardShellStore.ShellType[] getLinuxShells() { + return new StandardShellStore.ShellType[]{SH}; + } - @Override - public Charset getCharset() { - 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 getOperatingSystemNameCommand() { - return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\""); - } - }; - - @JsonProperty("cmd") - public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() { + @JsonTypeName("cmd") + @Value + public static class Cmd implements StandardShellStore.ShellType { @Override public NewLine getNewLine() { @@ -98,6 +77,8 @@ public class ShellTypes { var l = new ArrayList<>(cmd); l.add(0, "cmd.exe"); l.add(1, "/c"); + l.add(2, "@chcp 65001>nul"); + l.add(3, "&&"); return l; } @@ -123,9 +104,8 @@ public class ShellTypes { } @Override - public Charset getCharset() { - // TODO - return StandardCharsets.ISO_8859_1; + public Charset determineCharset(ShellStore store) throws Exception { + return StandardCharsets.UTF_8; } @Override @@ -142,10 +122,63 @@ public class ShellTypes { public List getOperatingSystemNameCommand() { return List.of("Get-ComputerInfo"); } - }; + } - @JsonProperty("sh") - public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() { + @JsonTypeName("powershell") + @Value + public static class PowerShell implements StandardShellStore.ShellType { + + @Override + public List switchTo(List cmd) { + var l = new ArrayList<>(cmd); + l.add(0, "powershell.exe"); + return l; + } + + @Override + public List createFileReadCommand(String file) { + return List.of("Get-Content", file); + } + + @Override + public List createFileWriteCommand(String file) { + return List.of("Out-File", "-FilePath", file); + } + + @Override + public List 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 getOperatingSystemNameCommand() { + return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\""); + } + } + + @JsonTypeName("sh") + @Value + public static class Sh implements StandardShellStore.ShellType { @Override public List switchTo(List cmd) { @@ -157,7 +190,7 @@ public class ShellTypes { var l = new ArrayList<>(cmd); l.add(0, "sudo"); 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); } @@ -177,7 +210,7 @@ public class ShellTypes { } @Override - public Charset getCharset() { + public Charset determineCharset(ShellStore st) throws Exception { return StandardCharsets.UTF_8; } @@ -200,32 +233,5 @@ public class ShellTypes { public List getOperatingSystemNameCommand() { 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; } } diff --git a/core/src/main/java/io/xpipe/core/store/StandardShellStore.java b/core/src/main/java/io/xpipe/core/store/StandardShellStore.java index 491d0e228..45f90720d 100644 --- a/core/src/main/java/io/xpipe/core/store/StandardShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/StandardShellStore.java @@ -1,5 +1,6 @@ package io.xpipe.core.store; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.xpipe.core.charsetter.NewLine; import io.xpipe.core.util.Secret; @@ -10,7 +11,7 @@ import java.util.List; public abstract class StandardShellStore extends ShellStore implements MachineFileStore { - + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public static interface ShellType { List switchTo(List cmd); @@ -25,7 +26,7 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi List createFileExistsCommand(String file); - Charset getCharset(); + Charset determineCharset(ShellStore store) throws Exception; NewLine getNewLine(); diff --git a/core/src/main/java/io/xpipe/core/store/StdinDataStore.java b/core/src/main/java/io/xpipe/core/store/StdinDataStore.java index f30481618..dde362ea2 100644 --- a/core/src/main/java/io/xpipe/core/store/StdinDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StdinDataStore.java @@ -14,7 +14,7 @@ import java.io.OutputStream; public class StdinDataStore implements StreamDataStore { @Override - public boolean isLocalToApplication() { + public boolean isContentExclusivelyAccessible() { return true; } diff --git a/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java b/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java index b7cc67284..3d9752c52 100644 --- a/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java @@ -13,7 +13,12 @@ import java.io.OutputStream; public class StdoutDataStore implements StreamDataStore { @Override - public boolean isLocalToApplication() { + public boolean canOpen() throws Exception { + return false; + } + + @Override + public boolean isContentExclusivelyAccessible() { return true; } diff --git a/core/src/main/java/io/xpipe/core/store/StreamDataStore.java b/core/src/main/java/io/xpipe/core/store/StreamDataStore.java index 2ef11eca6..eaa18fc77 100644 --- a/core/src/main/java/io/xpipe/core/store/StreamDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StreamDataStore.java @@ -35,13 +35,4 @@ public interface StreamDataStore extends DataStore { default OutputStream openOutput() throws Exception { 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; - } } diff --git a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java index 06cba56e5..8f663d64f 100644 --- a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java +++ b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; @@ -19,6 +20,7 @@ import io.xpipe.core.dialog.BaseQueryElement; import io.xpipe.core.dialog.BusyElement; import io.xpipe.core.dialog.ChoiceElement; import io.xpipe.core.dialog.HeaderElement; +import io.xpipe.core.source.DataSource; import io.xpipe.core.source.DataSourceInfo; import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.store.*; @@ -46,6 +48,10 @@ public class CoreJacksonModule extends SimpleModule { new NamedType(ArrayType.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.Structure.class), new NamedType(DataSourceInfo.Text.class), @@ -75,8 +81,33 @@ public class CoreJacksonModule extends SimpleModule { context.addSerializers(_serializers); 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 { + public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeNull(); + } + } + + public static class NullDeserializer extends JsonDeserializer { + + @Override + public DataSource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return null; + } + } + + public static class DataSourceReferenceSerializer extends JsonSerializer { @Override diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 90aa85aff..37d62bdce 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -9,8 +9,11 @@ open module io.xpipe.core { exports io.xpipe.core.data.node; exports io.xpipe.core.data.typed; exports io.xpipe.core.dialog; + exports io.xpipe.core.impl; 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.databind; requires java.net.http; diff --git a/extension/build.gradle b/extension/build.gradle index c2d836a02..29b03526f 100644 --- a/extension/build.gradle +++ b/extension/build.gradle @@ -22,16 +22,19 @@ dependencies { api project(':core') api project(':beacon') 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" - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0" - implementation group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0" - implementation 'net.synedra:validatorfx:0.3.1' - implementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' - implementation 'com.jfoenix:jfoenix:9.0.10' - implementation 'io.xpipe:fxcomps:0.2.2' - implementation 'org.controlsfx:controlsfx:11.1.1' + compileOnly group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0" + compileOnly 'net.synedra:validatorfx:0.3.1' + compileOnly 'org.junit.jupiter:junit-jupiter-api:5.9.0' + compileOnly 'com.jfoenix:jfoenix:9.0.10' + compileOnly 'io.xpipe:fxcomps:0.2.2' + compileOnly 'org.controlsfx:controlsfx:11.1.1' + compileOnly 'org.apache.commons:commons-lang3:3.12.0' } + + apply from: 'publish.gradle' apply from: "$rootDir/deps/publish-base.gradle" diff --git a/extension/src/main/java/io/xpipe/extension/Cache.java b/extension/src/main/java/io/xpipe/extension/Cache.java new file mode 100644 index 000000000..27a742b05 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/Cache.java @@ -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 getValue(String key, Class type, Supplier notPresent); + + public static T get(String key, Class type, Supplier notPresent) { + return INSTANCE.getValue(key, type, notPresent); + } + + public static Optional getIfPresent(String key, Class type) { + return Optional.ofNullable(get(key, type, () -> null)); + } + + public void updateValue(String key, T val); + + public static void update(String key, T val) { + INSTANCE.updateValue(key, key); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java index 37a3d8d18..fdf22d076 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java @@ -86,8 +86,8 @@ public interface DataSourceProvider> { Dialog configDialog(T source, boolean all); - default boolean isHidden() { - return false; + default boolean shouldShow(DataSourceType type) { + return type == null || type == getPrimaryType(); } DataSourceType getPrimaryType(); diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java index 0e0764e6a..8f47285ff 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProviders.java @@ -6,22 +6,20 @@ import io.xpipe.core.store.FileStore; import io.xpipe.extension.event.ErrorEvent; import lombok.SneakyThrows; -import java.util.List; -import java.util.Optional; -import java.util.ServiceLoader; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class DataSourceProviders { - private static Set> ALL; + private static List> ALL; public static void init(ModuleLayer layer) { if (ALL == null) { ALL = ServiceLoader.load(layer, DataSourceProvider.class) .stream() .map(p -> (DataSourceProvider) p.get()) - .collect(Collectors.toSet()); + .sorted(Comparator.comparing(DataSourceProvider::getId)) + .collect(Collectors.toList()); ALL.removeIf(p -> { try { p.init(); @@ -36,7 +34,7 @@ public class DataSourceProviders { } } - public static DataSourceProvider getNativeDataSourceDescriptorForType(DataSourceType t) { + public static DataSourceProvider getInternalProviderForType(DataSourceType t) { try { return switch (t) { case TABLE -> DataSourceProviders.byId("xpbt"); @@ -161,7 +159,7 @@ public class DataSourceProviders { .findAny(); } - public static Set> getAll() { + public static List> getAll() { return ALL; } } diff --git a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java index 69478499b..e9ccc7438 100644 --- a/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataStoreProvider.java @@ -77,10 +77,6 @@ public interface DataStoreProvider { DataStore defaultStore(); - default String display(DataStore store) { - return store.toSummaryString(); - } - List getPossibleNames(); default String getId() { @@ -89,7 +85,7 @@ public interface DataStoreProvider { List> getStoreClasses(); - default DataStoreFlow[] getPossibleFlows() { - return new DataStoreFlow[]{DataStoreFlow.INPUT}; + default DataFlow[] getPossibleFlows() { + return new DataFlow[]{DataFlow.INPUT}; } } diff --git a/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java b/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java index 8e3c712c8..06849f2d7 100644 --- a/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java +++ b/extension/src/main/java/io/xpipe/extension/DataStoreProviders.java @@ -4,20 +4,19 @@ import io.xpipe.core.dialog.Dialog; import io.xpipe.core.store.DataStore; import io.xpipe.extension.event.ErrorEvent; -import java.util.Objects; -import java.util.Optional; -import java.util.ServiceLoader; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class DataStoreProviders { - private static Set ALL; + private static List ALL; public static void init(ModuleLayer layer) { if (ALL == null) { 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 -> { try { return !p.init(); @@ -70,7 +69,7 @@ public class DataStoreProviders { return (T) ALL.stream().filter(d -> d.getStoreClasses().contains(c)).findAny().orElseThrow(); } - public static Set getAll() { + public static List getAll() { return ALL; } } diff --git a/extension/src/main/java/io/xpipe/extension/ExtensionTest.java b/extension/src/main/java/io/xpipe/extension/ExtensionTest.java deleted file mode 100644 index bfa1eb2e4..000000000 --- a/extension/src/main/java/io/xpipe/extension/ExtensionTest.java +++ /dev/null @@ -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 { - } -} diff --git a/extension/src/main/java/io/xpipe/extension/GuiDialog.java b/extension/src/main/java/io/xpipe/extension/GuiDialog.java index fbab46ff7..289115660 100644 --- a/extension/src/main/java/io/xpipe/extension/GuiDialog.java +++ b/extension/src/main/java/io/xpipe/extension/GuiDialog.java @@ -1,5 +1,7 @@ package io.xpipe.extension; +import io.xpipe.extension.util.SimpleValidator; +import io.xpipe.extension.util.Validator; import io.xpipe.fxcomps.Comp; import lombok.AllArgsConstructor; import lombok.Value; diff --git a/extension/src/main/java/io/xpipe/extension/I18n.java b/extension/src/main/java/io/xpipe/extension/I18n.java index 9115806a6..c3c9fbcf2 100644 --- a/extension/src/main/java/io/xpipe/extension/I18n.java +++ b/extension/src/main/java/io/xpipe/extension/I18n.java @@ -4,23 +4,20 @@ import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableValue; import java.util.ServiceLoader; -import java.util.function.Supplier; public interface I18n { I18n INSTANCE = ServiceLoader.load(I18n.class).findFirst().orElseThrow(); - public static Supplier resolver(String s, Object... vars) { - return () -> get(s, vars); - } public static ObservableValue observable(String s, Object... vars) { if (s == null) { return null; } + var key = INSTANCE.getKey(s); return Bindings.createStringBinding(() -> { - return get(s, vars); + return get(key, vars); }); } @@ -28,5 +25,7 @@ public interface I18n { return INSTANCE.getLocalised(s, vars); } + String getKey(String s); + String getLocalised(String s, Object... vars); } diff --git a/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java b/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java index 7b0f5d94b..b4aac7197 100644 --- a/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java +++ b/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java @@ -1,6 +1,7 @@ package io.xpipe.extension; import io.xpipe.core.source.DataSource; +import io.xpipe.extension.util.Validator; import javafx.beans.value.ObservableValue; import javafx.scene.layout.Region; import lombok.AllArgsConstructor; @@ -24,10 +25,12 @@ public interface SupportedApplicationProvider { public static class InstructionsDisplay { Region region; Runnable onFinish; + Validator validator; public InstructionsDisplay(Region region) { this.region = region; onFinish = null; + validator = null; } } diff --git a/extension/src/main/java/io/xpipe/extension/Translatable.java b/extension/src/main/java/io/xpipe/extension/Translatable.java index f8d5e6e61..6c4bf83f0 100644 --- a/extension/src/main/java/io/xpipe/extension/Translatable.java +++ b/extension/src/main/java/io/xpipe/extension/Translatable.java @@ -11,11 +11,13 @@ public interface Translatable { static StringConverter stringConverter() { return new StringConverter<>() { - @Override public String toString(T t) { + @Override + public String toString(T t) { return t == null ? null : t.toTranslatedString(); } - @Override public T fromString(String string) { + @Override + public T fromString(String string) { throw new AssertionError(); } }; diff --git a/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java b/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java deleted file mode 100644 index b3a77fdd3..000000000 --- a/extension/src/main/java/io/xpipe/extension/XPipeDaemon.java +++ /dev/null @@ -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 getNamedStores(); - - public Image image(String file); - - Comp streamStoreChooser(Property storeProperty, Property> provider); - - Comp namedStoreChooser(ObservableValue> filter, Property selected, DataStoreProvider.Category category); - - Comp namedSourceChooser(ObservableValue>> filter, Property> selected, DataSourceProvider.Category category); - - Comp sourceProviderChooser(Property> provider, DataSourceProvider.Category category); - - Optional getNamedStore(String name); - - Optional> getSource(String id); - - Optional getStoreName(DataStore store); - - Optional getSourceId(DataSource source); -} diff --git a/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java index 6a50e3f5e..afdefc96f 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java @@ -2,6 +2,7 @@ package io.xpipe.extension.comp; import io.xpipe.core.charsetter.StreamCharset; import io.xpipe.extension.I18n; +import io.xpipe.extension.util.CustomComboBoxBuilder; import io.xpipe.fxcomps.SimpleComp; import javafx.beans.property.Property; import javafx.scene.control.Label; diff --git a/extension/src/main/java/io/xpipe/extension/comp/CodeSnippet.java b/extension/src/main/java/io/xpipe/extension/comp/CodeSnippet.java index ed3fda41d..10542159c 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/CodeSnippet.java +++ b/extension/src/main/java/io/xpipe/extension/comp/CodeSnippet.java @@ -8,6 +8,13 @@ import java.util.stream.Collectors; public record CodeSnippet(List lines) { + public static final ColorScheme LIGHT_MODE = new ColorScheme( + Color.valueOf("0033B3"), + Color.valueOf("000000"), + Color.valueOf("000000"), + Color.valueOf("067D17") + ); + public String toString() { return getRawString(); } @@ -18,7 +25,11 @@ public record CodeSnippet(List lines) { .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); } @@ -31,11 +42,11 @@ public record CodeSnippet(List lines) { public static class Builder { - private CodeSnippets.ColorScheme scheme; + private ColorScheme scheme; private List lines; private List currentLine; - public Builder(CodeSnippets.ColorScheme scheme) { + public Builder(ColorScheme scheme) { this.scheme = scheme; lines = new ArrayList<>(); currentLine = new ArrayList<>(); @@ -115,4 +126,9 @@ public record CodeSnippet(List lines) { public static record Line(List elements) { } + + public static record ColorScheme( + Color keyword, Color identifier, Color type, Color string) { + + } } diff --git a/extension/src/main/java/io/xpipe/extension/comp/CodeSnippets.java b/extension/src/main/java/io/xpipe/extension/comp/CodeSnippets.java deleted file mode 100644 index e5d000535..000000000 --- a/extension/src/main/java/io/xpipe/extension/comp/CodeSnippets.java +++ /dev/null @@ -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) { - - } -} diff --git a/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java index 0c02bd111..7c19bb0b3 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/DataStoreFlowChoiceComp.java @@ -1,6 +1,6 @@ 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.fxcomps.SimpleComp; import javafx.beans.property.Property; @@ -15,15 +15,15 @@ import java.util.LinkedHashMap; @EqualsAndHashCode(callSuper = true) public class DataStoreFlowChoiceComp extends SimpleComp { - Property selected; - DataStoreFlow[] available; + Property selected; + DataFlow[] available; @Override protected Region createSimple() { - var map = new LinkedHashMap>(); - map.put(DataStoreFlow.INPUT, I18n.observable("extension.input")); - map.put(DataStoreFlow.OUTPUT, I18n.observable("extension.output")); - map.put(DataStoreFlow.INOUT, I18n.observable("extension.inout")); + var map = new LinkedHashMap>(); + map.put(DataFlow.INPUT, I18n.observable("extension.input")); + map.put(DataFlow.OUTPUT, I18n.observable("extension.output")); + map.put(DataFlow.INPUT_OUTPUT, I18n.observable("extension.inout")); return new ToggleGroupComp<>(selected, map).apply(struc -> { new FancyTooltipAugment<>("extension.inputDescription").augment(struc.get().getChildren().get(0)); new FancyTooltipAugment<>("extension.outputDescription").augment(struc.get().getChildren().get(1)); diff --git a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java index 23a6d1cbd..3ac3af803 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java @@ -41,6 +41,7 @@ public class DynamicOptionsComp extends Comp> { for (var entry : getEntries()) { var line = new HBox(); + line.setFillHeight(true); if (!wrap) { line.prefWidthProperty().bind(flow.widthProperty()); } diff --git a/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java b/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java index ce18b0b76..7305e4d2e 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java +++ b/extension/src/main/java/io/xpipe/extension/comp/FancyTooltipAugment.java @@ -5,15 +5,13 @@ import io.xpipe.extension.I18n; import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.Shortcuts; import io.xpipe.fxcomps.augment.Augment; -import javafx.beans.property.SimpleObjectProperty; +import io.xpipe.fxcomps.util.PlatformThread; import javafx.beans.value.ObservableValue; import javafx.scene.Node; import javafx.scene.layout.Region; import javafx.stage.Window; import javafx.util.Duration; -import java.util.function.Supplier; - public class FancyTooltipAugment> implements Augment { static { @@ -23,8 +21,8 @@ public class FancyTooltipAugment> implements Augment< private final ObservableValue text; - public FancyTooltipAugment(Supplier text) { - this.text = new SimpleObjectProperty<>(text.get()); + public FancyTooltipAugment(ObservableValue text) { + this.text = PlatformThread.sync(text); } public FancyTooltipAugment(String key) { @@ -73,8 +71,48 @@ public class FancyTooltipAugment> implements Augment< @Override protected void show() { Window owner = getOwnerWindow(); - if (owner.isFocused()) + if (owner == null || owner.isFocused()) { 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); + } } } } diff --git a/extension/src/main/java/io/xpipe/extension/comp/MultiVariantComp.java b/extension/src/main/java/io/xpipe/extension/comp/MultiVariantComp.java new file mode 100644 index 000000000..66ba3e9bf --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/comp/MultiVariantComp.java @@ -0,0 +1,4 @@ +package io.xpipe.extension.comp; + +public class MultiVariantComp { +} diff --git a/extension/src/main/java/io/xpipe/extension/comp/WriteModeChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/WriteModeChoiceComp.java new file mode 100644 index 000000000..9cc82e597 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/comp/WriteModeChoiceComp.java @@ -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 selected; + WriteMode[] available; + Validator validator = new SimpleValidator(); + Check check; + + public WriteModeChoiceComp(Property 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>(); + 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(); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/event/ExceptionConverter.java b/extension/src/main/java/io/xpipe/extension/event/ExceptionConverter.java index 8f4c73853..7ba574a61 100644 --- a/extension/src/main/java/io/xpipe/extension/event/ExceptionConverter.java +++ b/extension/src/main/java/io/xpipe/extension/event/ExceptionConverter.java @@ -8,10 +8,19 @@ public class ExceptionConverter { public static String convertMessage(Throwable ex) { var msg = ex.getLocalizedMessage(); + if (ex instanceof StackOverflowError) { + return I18n.get("extension.stackOverflow", msg); + } + if (ex instanceof FileNotFoundException) { return I18n.get("extension.fileNotFound", msg); } + if (ex instanceof UnsupportedOperationException) { + return I18n.get("extension.unsupportedOperation", msg); + } + + if (ex instanceof ClassNotFoundException) { return I18n.get("extension.classNotFound", msg); } diff --git a/extension/src/main/java/io/xpipe/extension/ChainedValidator.java b/extension/src/main/java/io/xpipe/extension/util/ChainedValidator.java similarity index 96% rename from extension/src/main/java/io/xpipe/extension/ChainedValidator.java rename to extension/src/main/java/io/xpipe/extension/util/ChainedValidator.java index a00932960..846a280b3 100644 --- a/extension/src/main/java/io/xpipe/extension/ChainedValidator.java +++ b/extension/src/main/java/io/xpipe/extension/util/ChainedValidator.java @@ -1,4 +1,4 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import javafx.beans.Observable; import javafx.beans.binding.Bindings; @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -public class ChainedValidator implements io.xpipe.extension.Validator { +public class ChainedValidator implements Validator { private final List validators; private final ReadOnlyObjectWrapper validationResultProperty = new ReadOnlyObjectWrapper<>(new ValidationResult()); diff --git a/extension/src/main/java/io/xpipe/extension/comp/CustomComboBoxBuilder.java b/extension/src/main/java/io/xpipe/extension/util/CustomComboBoxBuilder.java similarity index 98% rename from extension/src/main/java/io/xpipe/extension/comp/CustomComboBoxBuilder.java rename to extension/src/main/java/io/xpipe/extension/util/CustomComboBoxBuilder.java index 031577fbd..f759b6798 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/CustomComboBoxBuilder.java +++ b/extension/src/main/java/io/xpipe/extension/util/CustomComboBoxBuilder.java @@ -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 javafx.application.Platform; import javafx.beans.property.Property; diff --git a/extension/src/main/java/io/xpipe/extension/DialogHelper.java b/extension/src/main/java/io/xpipe/extension/util/DialogHelper.java similarity index 97% rename from extension/src/main/java/io/xpipe/extension/DialogHelper.java rename to extension/src/main/java/io/xpipe/extension/util/DialogHelper.java index d9fc2e37a..9776ea499 100644 --- a/extension/src/main/java/io/xpipe/extension/DialogHelper.java +++ b/extension/src/main/java/io/xpipe/extension/util/DialogHelper.java @@ -1,4 +1,4 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import io.xpipe.core.charsetter.NewLine; 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); } diff --git a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java similarity index 94% rename from extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java rename to extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java index 44200343b..59d8fb76e 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsBuilder.java +++ b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java @@ -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.StreamCharset; import io.xpipe.core.util.Secret; import io.xpipe.extension.I18n; -import io.xpipe.extension.Validator; -import io.xpipe.extension.Validators; +import io.xpipe.extension.comp.*; import io.xpipe.fxcomps.Comp; import javafx.beans.property.Property; import javafx.beans.value.ObservableValue; @@ -42,6 +41,9 @@ public class DynamicOptionsBuilder { this.wrap = false; this.title = title; } + public DynamicOptionsBuilder addTitle(String titleKey) { + return addTitle(I18n.observable(titleKey)); + } public DynamicOptionsBuilder addTitle(ObservableValue title) { entries.add(new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title"))); @@ -72,7 +74,7 @@ public class DynamicOptionsBuilder { for (var e : NewLine.values()) { 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)); props.add(prop); return this; @@ -149,6 +151,14 @@ public class DynamicOptionsBuilder { return this; } + public DynamicOptionsBuilder addComp(Comp comp) { + return addComp((ObservableValue) null, comp, null); + } + + public DynamicOptionsBuilder addComp(Comp comp, Property prop) { + return addComp((ObservableValue) null, comp, prop); + } + public DynamicOptionsBuilder addComp(String nameKey, Comp comp, Property prop) { return addComp(I18n.observable(nameKey), comp, prop); } diff --git a/extension/src/main/java/io/xpipe/extension/ExclusiveValidator.java b/extension/src/main/java/io/xpipe/extension/util/ExclusiveValidator.java similarity index 95% rename from extension/src/main/java/io/xpipe/extension/ExclusiveValidator.java rename to extension/src/main/java/io/xpipe/extension/util/ExclusiveValidator.java index cdc4e0406..cabbeff4b 100644 --- a/extension/src/main/java/io/xpipe/extension/ExclusiveValidator.java +++ b/extension/src/main/java/io/xpipe/extension/util/ExclusiveValidator.java @@ -1,4 +1,4 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import javafx.beans.Observable; import javafx.beans.binding.Bindings; @@ -12,7 +12,7 @@ import net.synedra.validatorfx.ValidationResult; import java.util.ArrayList; import java.util.Map; -public final class ExclusiveValidator implements io.xpipe.extension.Validator { +public final class ExclusiveValidator implements Validator { private final Map validators; private final ObservableValue obs; diff --git a/extension/src/main/java/io/xpipe/extension/util/ExtensionJacksonModule.java b/extension/src/main/java/io/xpipe/extension/util/ExtensionJacksonModule.java index 236af3269..0f9f86715 100644 --- a/extension/src/main/java/io/xpipe/extension/util/ExtensionJacksonModule.java +++ b/extension/src/main/java/io/xpipe/extension/util/ExtensionJacksonModule.java @@ -43,8 +43,12 @@ public class ExtensionJacksonModule extends SimpleModule { var mapper = JacksonHelper.newMapper(ExtensionJacksonModule.class); var tree = (ObjectNode) mapper.readTree(p); var type = tree.get("type").textValue(); - var prov = DataSourceProviders.byId(type); - return mapper.treeToValue(tree, prov.getSourceClass()); + var prov = DataSourceProviders.byName(type); + if (prov.isEmpty()) { + return null; + } + + return mapper.treeToValue(tree, prov.get().getSourceClass()); } } } diff --git a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java new file mode 100644 index 000000000..cba1a02b9 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java @@ -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(); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/util/ModuleHelper.java b/extension/src/main/java/io/xpipe/extension/util/ModuleHelper.java deleted file mode 100644 index 673ed7afc..000000000 --- a/extension/src/main/java/io/xpipe/extension/util/ModuleHelper.java +++ /dev/null @@ -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); - } -} diff --git a/extension/src/main/java/io/xpipe/extension/util/PrettyListView.java b/extension/src/main/java/io/xpipe/extension/util/PrettyListView.java new file mode 100644 index 000000000..7c62d1a68 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/PrettyListView.java @@ -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 extends ListView { + + 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 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); + } +} \ No newline at end of file diff --git a/extension/src/main/java/io/xpipe/extension/Properties.java b/extension/src/main/java/io/xpipe/extension/util/PropertiesHelper.java similarity index 84% rename from extension/src/main/java/io/xpipe/extension/Properties.java rename to extension/src/main/java/io/xpipe/extension/util/PropertiesHelper.java index 5d79cb2ac..e86cd6752 100644 --- a/extension/src/main/java/io/xpipe/extension/Properties.java +++ b/extension/src/main/java/io/xpipe/extension/util/PropertiesHelper.java @@ -1,10 +1,10 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import javafx.beans.property.Property; import java.util.Map; -public class Properties { +public class PropertiesHelper { public static void bindExclusive(Property selected, Map> map, Property toBind) { selected.addListener((c,o,n) -> { diff --git a/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/util/SimpleFileDataSourceProvider.java similarity index 93% rename from extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java rename to extension/src/main/java/io/xpipe/extension/util/SimpleFileDataSourceProvider.java index 859099134..958c92f91 100644 --- a/extension/src/main/java/io/xpipe/extension/SimpleFileDataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/util/SimpleFileDataSourceProvider.java @@ -1,10 +1,13 @@ -package io.xpipe.extension; +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.core.store.FilenameStore; 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.List; diff --git a/extension/src/main/java/io/xpipe/extension/SimpleValidator.java b/extension/src/main/java/io/xpipe/extension/util/SimpleValidator.java similarity index 99% rename from extension/src/main/java/io/xpipe/extension/SimpleValidator.java rename to extension/src/main/java/io/xpipe/extension/util/SimpleValidator.java index 51471c2ab..72b388236 100644 --- a/extension/src/main/java/io/xpipe/extension/SimpleValidator.java +++ b/extension/src/main/java/io/xpipe/extension/util/SimpleValidator.java @@ -1,4 +1,4 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import javafx.beans.binding.Bindings; import javafx.beans.binding.StringBinding; diff --git a/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java b/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java new file mode 100644 index 000000000..ebfc7d0d3 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java @@ -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); + } + } + } +} diff --git a/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/util/UniformDataSourceProvider.java similarity index 86% rename from extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java rename to extension/src/main/java/io/xpipe/extension/util/UniformDataSourceProvider.java index bb96e8c93..e6ebbd816 100644 --- a/extension/src/main/java/io/xpipe/extension/UniformDataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/util/UniformDataSourceProvider.java @@ -1,8 +1,10 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import io.xpipe.core.dialog.Dialog; import io.xpipe.core.source.DataSource; import io.xpipe.core.store.DataStore; +import io.xpipe.extension.DataSourceProvider; +import io.xpipe.extension.ExtensionException; import java.lang.reflect.InvocationTargetException; diff --git a/extension/src/main/java/io/xpipe/extension/util/Validatable.java b/extension/src/main/java/io/xpipe/extension/util/Validatable.java new file mode 100644 index 000000000..eff915560 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/Validatable.java @@ -0,0 +1,6 @@ +package io.xpipe.extension.util; + +public interface Validatable { + + Validator getValidator(); +} diff --git a/extension/src/main/java/io/xpipe/extension/Validator.java b/extension/src/main/java/io/xpipe/extension/util/Validator.java similarity index 98% rename from extension/src/main/java/io/xpipe/extension/Validator.java rename to extension/src/main/java/io/xpipe/extension/util/Validator.java index 54b353707..968205a3d 100644 --- a/extension/src/main/java/io/xpipe/extension/Validator.java +++ b/extension/src/main/java/io/xpipe/extension/util/Validator.java @@ -1,4 +1,4 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import javafx.beans.binding.StringBinding; import javafx.beans.property.ReadOnlyBooleanProperty; diff --git a/extension/src/main/java/io/xpipe/extension/Validators.java b/extension/src/main/java/io/xpipe/extension/util/Validators.java similarity index 93% rename from extension/src/main/java/io/xpipe/extension/Validators.java rename to extension/src/main/java/io/xpipe/extension/util/Validators.java index 932f18e14..382869f06 100644 --- a/extension/src/main/java/io/xpipe/extension/Validators.java +++ b/extension/src/main/java/io/xpipe/extension/util/Validators.java @@ -1,6 +1,7 @@ -package io.xpipe.extension; +package io.xpipe.extension.util; import io.xpipe.core.store.ShellStore; +import io.xpipe.extension.I18n; import javafx.beans.value.ObservableValue; import net.synedra.validatorfx.Check; diff --git a/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java b/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java new file mode 100644 index 000000000..20693d83a --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/XPipeDaemon.java @@ -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 getNamedStores(); + + public Image image(String file); + + & Validatable> T streamStoreChooser(Property storeProperty, Property> provider); + + & Validatable> T namedStoreChooser( + ObservableValue> filter, Property selected, DataStoreProvider.Category category + ); + + Comp namedSourceChooser( + ObservableValue>> filter, Property> selected, DataSourceProvider.Category category + ); + + & Validatable> T sourceProviderChooser(Property> provider, DataSourceProvider.Category category, DataSourceType filter); + + Optional getNamedStore(String name); + + Optional> getSource(String id); + + Optional getStoreName(DataStore store); + + Optional getSourceId(DataSource source); +} diff --git a/extension/src/main/java/module-info.java b/extension/src/main/java/module-info.java index e46d57d84..65bd3eef5 100644 --- a/extension/src/main/java/module-info.java +++ b/extension/src/main/java/module-info.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.Module; import io.xpipe.extension.DataSourceProvider; import io.xpipe.extension.SupportedApplicationProvider; import io.xpipe.extension.util.ExtensionJacksonModule; +import io.xpipe.extension.util.XPipeDaemon; open module 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.prefs; exports io.xpipe.extension.util; - exports io.xpipe.extension.test; requires transitive io.xpipe.core; requires io.xpipe.beacon; requires io.xpipe.api; requires com.fasterxml.jackson.databind; 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.controls; requires static io.xpipe.fxcomps; @@ -38,7 +39,8 @@ open module io.xpipe.extension { uses io.xpipe.extension.event.EventHandler; uses io.xpipe.extension.prefs.PrefsProvider; uses io.xpipe.extension.DataStoreProvider; - uses io.xpipe.extension.XPipeDaemon; + uses XPipeDaemon; + uses io.xpipe.extension.Cache; provides Module with ExtensionJacksonModule; } \ No newline at end of file diff --git a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties index 00df05f6d..3605c4fd7 100644 --- a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties +++ b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties @@ -15,4 +15,10 @@ output=Output inout=In/Out inputDescription=This store only produces input for data sources to read outputDescription=This store only accepts output from data sources to write -inoutDescription=This store uses both input and output to essentially create a data transformation \ No newline at end of file +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 \ No newline at end of file