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'