diff --git a/api/src/main/java/io/xpipe/api/DataSource.java b/api/src/main/java/io/xpipe/api/DataSource.java
index 2277d325..0d2bf198 100644
--- a/api/src/main/java/io/xpipe/api/DataSource.java
+++ b/api/src/main/java/io/xpipe/api/DataSource.java
@@ -41,8 +41,17 @@ public interface DataSource {
*
* @return the generator data source
*/
- @Deprecated
- static DataSource supplySource() {
+ static DataSource drain() {
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED!
+ *
+ * Creates a data source sink that will block with any read operations
+ * until an external data producer routes the output into this sink.
+ */
+ static DataSource sink() {
return null;
}
diff --git a/api/src/test/java/io/xpipe/api/test/ApiTest.java b/api/src/test/java/io/xpipe/api/test/ApiTest.java
index e4c08018..fe7fd482 100644
--- a/api/src/test/java/io/xpipe/api/test/ApiTest.java
+++ b/api/src/test/java/io/xpipe/api/test/ApiTest.java
@@ -1,6 +1,7 @@
package io.xpipe.api.test;
import io.xpipe.beacon.BeaconDaemonController;
+import io.xpipe.core.util.XPipeDaemonMode;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -8,7 +9,7 @@ public class ApiTest {
@BeforeAll
public static void setup() throws Exception {
- BeaconDaemonController.start();
+ BeaconDaemonController.start(XPipeDaemonMode.TRAY);
}
@AfterAll
diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java
index bf9a79d0..79b8dce1 100644
--- a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java
+++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java
@@ -46,7 +46,7 @@ public class BeaconServer {
command = XPipeInstallation.createExternalAsyncLaunchCommand(installationBase, mode, BeaconConfig.getDaemonArguments());
} else {
command = XPipeInstallation.createExternalLaunchCommand(
- getDaemonDebugExecutable(installationBase), BeaconConfig.getDaemonArguments());
+ getDaemonDebugExecutable(installationBase), BeaconConfig.getDaemonArguments(), mode);
}
var fullCommand = ShellTypes.getPlatformDefault().executeCommandListWithShell(command);
diff --git a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java
index e0182e42..1a970687 100644
--- a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java
+++ b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java
@@ -3,7 +3,6 @@ package io.xpipe.core.charsetter;
import io.xpipe.core.util.Identifiers;
import lombok.Value;
-import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -56,20 +55,10 @@ public class StreamCharset {
Identifiers.get("utf", "16", "le", "bom")
);
- public static final StreamCharset UTF16 =
- new StreamCharset(StandardCharsets.UTF_16, null, Identifiers.get("utf", "16"));
+ public static final StreamCharset UTF16 = UTF16_LE;
- public static final StreamCharset UTF16_BOM = new StreamCharset(
- StandardCharsets.UTF_16,
- ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)
- ? UTF16_BE_BOM.getByteOrderMark()
- : UTF16_LE_BOM.getByteOrderMark(),
- Identifiers.get("utf", "16", "bom")
- );
+ public static final StreamCharset UTF16_BOM = UTF16_LE_BOM;
- // ======
- // UTF-32
- // ======
public static final List COMMON = List.of(
UTF8,
UTF8_BOM,
@@ -101,6 +90,10 @@ public class StreamCharset {
Identifiers.join(Identifiers.get("windows", "1252"), Identifiers.get("1252"))
)
);
+
+ // ======
+ // UTF-32
+ // ======
public static final StreamCharset UTF32_LE =
new StreamCharset(Charset.forName("utf-32le"), null, Identifiers.get("utf", "32", "le"));
public static final StreamCharset UTF32_LE_BOM = new StreamCharset(
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 70b579fb..8658271f 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
@@ -57,6 +57,7 @@ public abstract class ArrayNode extends DataStructureNode {
return "array node";
}
+
@Override
public final String toString(int indent) {
var content = getNodes().stream().map(n -> n.toString(indent)).collect(Collectors.joining(", "));
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 2424da24..88bed717 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
@@ -1,9 +1,8 @@
package io.xpipe.core.data.node;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
public abstract class TupleNode extends DataStructureNode {
@@ -41,6 +40,21 @@ public abstract class TupleNode extends DataStructureNode {
return true;
}
+ @Override
+ public Stream stream() {
+ return getNodes().stream();
+ }
+
+ @Override
+ public Spliterator spliterator() {
+ return stream().spliterator();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return stream().iterator();
+ }
+
@Override
public String toString(int indent) {
var is = " ".repeat(indent);
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 255a2bad..975a103d 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
@@ -66,43 +66,57 @@ public abstract class ValueNode extends DataStructureNode {
public static ValueNode ofBytes(byte[] data) {
var created = of(data);
- created.tag(IS_BINARY);
+ if (data != null) {
+ created.tag(IS_BINARY);}
return created;
}
public static ValueNode ofText(String text) {
var created = of(text);
- created.tag(IS_TEXT);
+ if (text != null) {
+ created.tag(IS_TEXT);
+ }
return created;
}
public static ValueNode ofInteger(int integer) {
var created = of(integer);
created.tag(IS_INTEGER);
+ created.tag(INTEGER_VALUE, integer);
return created;
}
public static ValueNode ofInteger(BigInteger integer) {
var created = of(integer);
- created.tag(IS_INTEGER);
+ if (integer != null) {
+ created.tag(IS_INTEGER);
+ created.tag(INTEGER_VALUE, integer);
+ }
return created;
}
public static ValueNode ofDecimal(double decimal) {
var created = of(decimal);
created.tag(IS_DECIMAL);
+ created.tag(DECIMAL_VALUE, decimal);
return created;
}
public static ValueNode ofDecimal(BigDecimal decimal) {
var created = of(decimal);
- created.tag(IS_DECIMAL);
+ if (decimal != null) {
+ created.tag(IS_DECIMAL);
+ created.tag(DECIMAL_VALUE, decimal);
+ }
return created;
}
public static ValueNode ofBoolean(Boolean bool) {
var created = of(bool);
- created.tag(IS_BOOLEAN);
+ if (bool != null) {
+ created.tag(IS_BOOLEAN);
+ created.tag(bool ? BOOLEAN_TRUE : BOOLEAN_FALSE);
+ }
return created;
}
diff --git a/core/src/main/java/io/xpipe/core/impl/InMemoryStore.java b/core/src/main/java/io/xpipe/core/impl/InMemoryStore.java
index 3dff3592..48872983 100644
--- a/core/src/main/java/io/xpipe/core/impl/InMemoryStore.java
+++ b/core/src/main/java/io/xpipe/core/impl/InMemoryStore.java
@@ -1,10 +1,11 @@
package io.xpipe.core.impl;
-import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonizedValue;
+import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@@ -17,13 +18,15 @@ import java.io.*;
@SuperBuilder
@Jacksonized
@Getter
+@NoArgsConstructor
+@AllArgsConstructor
public class InMemoryStore extends JacksonizedValue implements StreamDataStore {
private byte[] value;
- @JsonCreator
- public InMemoryStore(byte[] value) {
- this.value = value;
+ @Override
+ public String toString() {
+ return value != null && value.length > 100 ? "" : (value != null ? new String(value) : "null");
}
@Override
@@ -33,7 +36,7 @@ public class InMemoryStore extends JacksonizedValue implements StreamDataStore {
@Override
public InputStream openInput() throws Exception {
- return new ByteArrayInputStream(value);
+ return value != null ? new ByteArrayInputStream(value) : InputStream.nullInputStream();
}
@Override
diff --git a/core/src/main/java/io/xpipe/core/impl/LocalProcessControlProvider.java b/core/src/main/java/io/xpipe/core/impl/LocalProcessControlProvider.java
index e0460723..1c1c9465 100644
--- a/core/src/main/java/io/xpipe/core/impl/LocalProcessControlProvider.java
+++ b/core/src/main/java/io/xpipe/core/impl/LocalProcessControlProvider.java
@@ -20,10 +20,10 @@ public abstract class LocalProcessControlProvider {
INSTANCE = layer != null
? ServiceLoader.load(layer, LocalProcessControlProvider.class)
.findFirst()
- .orElseThrow()
+ .orElse(null)
: ServiceLoader.load(LocalProcessControlProvider.class)
.findFirst()
- .orElseThrow();
+ .orElse(null);
}
public static ShellProcessControl create() {
diff --git a/core/src/main/java/io/xpipe/core/impl/TextSource.java b/core/src/main/java/io/xpipe/core/impl/TextSource.java
index e21c8c7e..d1c88278 100644
--- a/core/src/main/java/io/xpipe/core/impl/TextSource.java
+++ b/core/src/main/java/io/xpipe/core/impl/TextSource.java
@@ -22,7 +22,16 @@ public final class TextSource extends TextDataSource implements
@Override
protected io.xpipe.core.source.TextWriteConnection newWriteConnection(WriteMode mode) {
- return new TextWriteConnection(this);
+ var sup = super.newWriteConnection(mode);
+ if (sup != null) {
+ return sup;
+ }
+
+ if (mode.equals(WriteMode.REPLACE)) {
+ return new TextWriteConnection(this);
+ }
+
+ throw new UnsupportedOperationException(mode.getId());
}
@Override
diff --git a/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java b/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java
index 5365323b..20e2bfcd 100644
--- a/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java
+++ b/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java
@@ -15,7 +15,7 @@ public class JacksonizedValue {
}
@SneakyThrows
- public final String toString() {
+ public String toString() {
var tree = JacksonMapper.getDefault().valueToTree(this);
return tree.toPrettyString();
}
diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java
index 3de4588f..3b783b02 100644
--- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java
+++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java
@@ -23,9 +23,9 @@ public class XPipeInstallation {
return "\"" + FileNames.join(installationBase, XPipeInstallation.getDaemonExecutablePath(OsType.getLocal())) + "\" --mode " + mode.getDisplayName() + suffix;
}
- public static String createExternalLaunchCommand(String command, String arguments) {
+ public static String createExternalLaunchCommand(String command, String arguments, XPipeDaemonMode mode) {
var suffix = (arguments != null ? " " + arguments : "");
- return "\"" + command + "\" --external" + suffix;
+ return "\"" + command + "\" --mode " + mode.getDisplayName() + suffix;
}
@SneakyThrows
diff --git a/extension/build.gradle b/extension/build.gradle
index 13c75650..58b7ea54 100644
--- a/extension/build.gradle
+++ b/extension/build.gradle
@@ -24,6 +24,7 @@ dependencies {
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"
+ compileOnly 'org.hamcrest:hamcrest:2.2'
compileOnly 'net.java.dev.jna:jna-jpms:5.12.1'
compileOnly 'net.java.dev.jna:jna-platform-jpms:5.12.1'
compileOnly group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
diff --git a/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java b/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java
index 9388e3de..757b3487 100644
--- a/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java
+++ b/extension/src/main/java/io/xpipe/extension/fxcomps/util/BindingsHelper.java
@@ -1,7 +1,6 @@
package io.xpipe.extension.fxcomps.util;
import javafx.beans.binding.Binding;
-import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -15,12 +14,12 @@ public class BindingsHelper {
/*
TODO: Proper cleanup. Maybe with a separate thread?
*/
- private static final Map, Set>> BINDINGS = new ConcurrentHashMap<>();
+ private static final Map, Set> BINDINGS = new ConcurrentHashMap<>();
public static > T persist(T binding) {
- var dependencies = new HashSet>();
+ var dependencies = new HashSet();
while (dependencies.addAll(binding.getDependencies().stream()
- .map(o -> (ObservableValue>) o)
+ .map(o -> (javafx.beans.Observable) o)
.toList())) {
}
dependencies.add(binding);
diff --git a/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java b/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java
index 951e2db1..4f1c4d80 100644
--- a/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java
+++ b/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java
@@ -4,13 +4,18 @@ import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.ValueNode;
import java.util.Currency;
+import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
public class DataTypeParser {
+ private static final Map currencies =
+ Currency.getAvailableCurrencies().stream().collect(Collectors.toMap(currency -> currency.getSymbol(), currency -> currency));
+
public static Optional parseMonetary(String val) {
- for (Currency availableCurrency : Currency.getAvailableCurrencies()) {
- if (val.contains(availableCurrency.getSymbol())) {
+ for (var availableCurrency : currencies.entrySet()) {
+ if (val.contains(availableCurrency.getKey())) {
String newStr = DataTypeParserInternal.cleanseNumberString(val);
var node = DataTypeParserInternal.parseDecimalFromCleansed(newStr);
if (node.isEmpty()) {
@@ -18,7 +23,7 @@ public class DataTypeParser {
}
return Optional.of(ValueNode.ofCurrency(
- val, node.get().getMetaAttribute(DataStructureNode.DECIMAL_VALUE), availableCurrency));
+ val, node.get().getMetaAttribute(DataStructureNode.DECIMAL_VALUE), availableCurrency.getValue()));
}
}
return Optional.empty();
diff --git a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java
index 98733faa..e66e2e05 100644
--- a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java
+++ b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java
@@ -1,13 +1,26 @@
package io.xpipe.extension.util;
+import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.store.DataStore;
import lombok.SneakyThrows;
+import org.junit.jupiter.api.Assertions;
import java.nio.file.Path;
public class ExtensionTest {
+ public static void assertNodeEquals(DataStructureNode expected, DataStructureNode actual) {
+ if (expected.isValue() || actual.isValue()) {
+ Assertions.assertEquals(expected, actual);
+ } else {
+ for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
+ Assertions.assertEquals(expected.getNodes().get(i), actual.getNodes().get(i));
+ }
+ Assertions.assertEquals(expected, actual);
+ }
+ }
+
@SneakyThrows
public static Path getResourcePath(Class> c, String name) {
var url = c.getResource(name);
diff --git a/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java b/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java
index 4c4d5986..b28c91c0 100644
--- a/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java
+++ b/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java
@@ -34,8 +34,10 @@ public class TypeConverter {
var number = NumberUtils.createNumber(string);
if (number instanceof Float || number instanceof Double) {
node.tag(DataStructureNode.IS_DECIMAL);
+ node.tag(DataStructureNode.DECIMAL_VALUE, number.doubleValue());
} else {
node.tag(DataStructureNode.IS_INTEGER);
+ node.tag(DataStructureNode.INTEGER_VALUE, number.intValue());
}
}
}
@@ -72,11 +74,11 @@ public class TypeConverter {
}
if (node.hasMetaAttribute(DataStructureNode.BOOLEAN_FALSE)) {
- return Boolean.parseBoolean(node.getMetaAttribute(DataStructureNode.BOOLEAN_FALSE));
+ return false;
}
if (node.hasMetaAttribute(DataStructureNode.BOOLEAN_TRUE)) {
- return Boolean.parseBoolean(node.getMetaAttribute(DataStructureNode.BOOLEAN_TRUE));
+ return true;
}
var string = node.asString();
diff --git a/extension/src/main/java/module-info.java b/extension/src/main/java/module-info.java
index 66afbdff..9c1c671e 100644
--- a/extension/src/main/java/module-info.java
+++ b/extension/src/main/java/module-info.java
@@ -17,6 +17,7 @@ open module io.xpipe.extension {
requires transitive io.xpipe.core;
requires io.xpipe.beacon;
requires io.xpipe.api;
+ requires static org.hamcrest;
requires com.fasterxml.jackson.databind;
requires static com.sun.jna;
requires static com.sun.jna.platform;
diff --git a/gradle_scripts/extension_test.gradle b/gradle_scripts/extension_test.gradle
index deb008f9..956f8635 100644
--- a/gradle_scripts/extension_test.gradle
+++ b/gradle_scripts/extension_test.gradle
@@ -1,3 +1,5 @@
+import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
+
apply from: "$buildscript.sourceFile/../junit.gradle"
def useExtension = System.getProperty('excludeExtensionLibrary') == null
@@ -33,8 +35,10 @@ test {
" -Dio.xpipe.beacon.printMessages=false" +
" -Dio.xpipe.app.logLevel=trace"
- if (findProject(':app') != null) {
- systemProperty "io.xpipe.beacon.customDaemonCommand", "cmd.exe /c start \"\"X-Pipe Debug\"\" /i \"$rootDir\\gradlew.bat\" --console=plain $daemonCommand"
+ // Use cmd window for tests
+ if (findProject(':app') != null && System.getenv("XPIPE_MAPPING") != null && DefaultNativePlatform.currentOperatingSystem.isWindows()) {
+ systemProperty "io.xpipe.beacon.customDaemonCommand",
+ "cmd.exe /c start \"\"X-Pipe Debug\"\" /i \"$rootDir\\gradlew.bat\" --console=plain $daemonCommand"
}
diff --git a/gradle_scripts/junit.gradle b/gradle_scripts/junit.gradle
index 4502a79a..69164d5b 100644
--- a/gradle_scripts/junit.gradle
+++ b/gradle_scripts/junit.gradle
@@ -1,4 +1,5 @@
dependencies {
+ testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'