Various reworks

This commit is contained in:
Christopher Schnick 2022-09-17 05:08:36 +02:00
parent 4d168b66c0
commit 5459347482
94 changed files with 1186 additions and 514 deletions

View file

@ -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"

View file

@ -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();
}

View file

@ -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 -> {

View file

@ -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;

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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;
}
}
};

View file

@ -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

View file

@ -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);

View file

@ -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);
}

View file

@ -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

View file

@ -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<DataStructureNode> {
@ -11,9 +12,15 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
public static final Integer KEY_TABLE_NAME = 1;
public static final Integer KEY_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<Integer, String> metaAttributes;
@ -109,6 +116,15 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
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() {

View file

@ -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<Integer, String> parseAttributes(InputStream in) throws IOException {
var attributesLength = in.read();
var attributesLength = parseShort(in);
if (attributesLength == 0) {
return null;
}
var map = new HashMap<Integer, String>();
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<Integer, String> 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 {

View file

@ -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<DataStructureNode> nodes;
@ -79,4 +79,6 @@ public class SimpleArrayNode extends ArrayNode {
nodes.remove(index);
return this;
}
}

View file

@ -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) ? "<null>" : new String(getRawData(), StandardCharsets.UTF_8);
return (hasMetaAttribute(TEXT) ? "\"" : "") + string + (hasMetaAttribute(TEXT) ? "\"" : "") + " " + metaToString();
}
@Override

View file

@ -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<DataStructureNode> 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<KeyValue> getKeyValuePairs() {
var l = new ArrayList<KeyValue>(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;
}

View file

@ -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<String> names, List<DataStructureNode> nodes) {
public static TupleNode of(List<String> names, List<? extends DataStructureNode> 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<DataStructureNode>) 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 {

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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<Consumer<?>> completion = new ArrayList<>();
/* TODO: Implement automatic completion mechanism for start as well
* In case start returns null, the completion is not automatically done.
* */
public abstract DialogElement start() throws Exception;
public Dialog evaluateTo(Dialog d) {

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -0,0 +1,32 @@
package io.xpipe.core.impl;
import io.xpipe.core.source.StreamReadConnection;
import java.io.BufferedReader;
import java.util.stream.Stream;
public class TextReadConnection extends StreamReadConnection implements io.xpipe.core.source.TextReadConnection {
private BufferedReader bufferedReader;
public TextReadConnection(TextSource source) {
super(source.getStore(), source.getCharset());
}
@Override
public void init() throws Exception {
super.init();
bufferedReader = new BufferedReader(reader);
}
@Override
public Stream<String> lines() throws Exception {
return bufferedReader.lines();
}
@Override
public void close() throws Exception {
bufferedReader.close();
}
}

View file

@ -0,0 +1,44 @@
package io.xpipe.core.impl;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.xpipe.core.charsetter.Charsettable;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.source.TextDataSource;
import io.xpipe.core.store.StreamDataStore;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper = true)
public class TextSource extends TextDataSource<StreamDataStore> implements Charsettable {
StreamCharset charset;
NewLine newLine;
public TextSource(StreamDataStore store){
this(store, StreamCharset.UTF8, NewLine.LF);
}
@JsonCreator
public TextSource(StreamDataStore store, StreamCharset charset, NewLine newLine) {
super(store);
this.charset = charset;
this.newLine = newLine;
}
@Override
protected io.xpipe.core.source.TextWriteConnection newWriteConnection() {
return new TextWriteConnection(this);
}
@Override
protected io.xpipe.core.source.TextWriteConnection newAppendingWriteConnection() {
return new PreservingTextWriteConnection(this, newWriteConnection(), true);
}
@Override
protected io.xpipe.core.source.TextReadConnection newReadConnection() {
return new TextReadConnection(this);
}
}

View file

@ -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());
}
}

View file

@ -0,0 +1,95 @@
package io.xpipe.core.impl;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
import io.xpipe.core.source.TableReadConnection;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonHelper;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class XpbtReadConnection implements TableReadConnection {
@Override
public void init() throws Exception {
this.inputStream = store.openBufferedInput();
this.inputStream.mark(8192);
var header = new BufferedReader(new InputStreamReader(inputStream)).readLine();
this.inputStream.reset();
if (header == null || header.trim().length() == 0) {
empty = true;
return;
}
var headerLength = header.getBytes(StandardCharsets.UTF_8).length;
this.inputStream.skip(headerLength);
List<String> names = JacksonHelper.newMapper()
.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
.readerFor(new TypeReference<List<String>>(){}).readValue(header);
TupleType dataType = TupleType.tableType(names);
this.dataType = dataType;
this.parser = new TypedDataStreamParser(dataType);
}
@Override
public void close() throws Exception {
inputStream.close();
}
private TupleType dataType;
private final StreamDataStore store;
private InputStream inputStream;
private TypedDataStreamParser parser;
private boolean empty;
protected XpbtReadConnection(StreamDataStore store) {
this.store = store;
}
@Override
public TupleType getDataType() {
return dataType;
}
@Override
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
if (empty) {
return;
}
var reader = TypedDataStructureNodeReader.of(dataType);
AtomicBoolean quit = new AtomicBoolean(false);
AtomicReference<Exception> exception = new AtomicReference<>();
while (!quit.get()) {
var node = parser.parseStructure(inputStream, reader);
if (node == null) {
quit.set(true);
break;
}
try {
if (!lineAcceptor.accept(node.asTuple())) {
quit.set(true);
}
} catch (Exception ex) {
quit.set(true);
exception.set(ex);
}
}
if (exception.get() != null) {
throw exception.get();
}
}
}

View file

@ -0,0 +1,25 @@
package io.xpipe.core.impl;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.xpipe.core.source.TableDataSource;
import io.xpipe.core.source.TableReadConnection;
import io.xpipe.core.source.TableWriteConnection;
import io.xpipe.core.store.StreamDataStore;
public class XpbtSource extends TableDataSource<StreamDataStore> {
@JsonCreator
public XpbtSource(StreamDataStore store) {
super(store);
}
@Override
protected TableWriteConnection newWriteConnection() {
return new XpbtWriteConnection(store);
}
@Override
protected TableReadConnection newReadConnection() {
return new XpbtReadConnection(store);
}
}

View file

@ -0,0 +1,65 @@
package io.xpipe.core.impl;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.typed.TypedDataStreamWriter;
import io.xpipe.core.source.TableWriteConnection;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.util.JacksonHelper;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class XpbtWriteConnection implements TableWriteConnection {
private final StreamDataStore store;
private OutputStream outputStream;
private TupleType writtenDescriptor;
public XpbtWriteConnection(StreamDataStore store) {
this.store = store;
}
@Override
public void init() throws Exception {
outputStream = store.openOutput();
}
@Override
public void close() throws Exception {
if (outputStream != null) {
outputStream.close();
}
}
@Override
public DataStructureNodeAcceptor<TupleNode> writeLinesAcceptor() {
return t -> {
writeDescriptor(t);
TypedDataStreamWriter.writeStructure(outputStream, t, writtenDescriptor);
return true;
};
}
private void writeDescriptor(TupleNode tupleNode) throws IOException {
if (writtenDescriptor != null) {
return;
}
writtenDescriptor = TupleType.tableType(tupleNode.getKeyNames());
var writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
JsonFactory f = new JsonFactory();
try (JsonGenerator g = f.createGenerator(writer)
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
.setPrettyPrinter(new DefaultPrettyPrinter())) {
JacksonHelper.newMapper().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
.writeValue(g, tupleNode.getKeyNames());
}
}
}

View file

@ -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.
*
* <p>
* 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<DS extends DataStore> {
public static DataSource<?> createInternalDataSource(DataSourceType t, DataStore store) {
try {
return switch (t) {
case TABLE -> new XpbtSource(store.asNeeded());
case STRUCTURE -> null;
case TEXT -> new TextSource(store.asNeeded());
case RAW -> null;
//TODO
case COLLECTION -> null;
};
} catch (Exception ex) {
throw new AssertionError(ex);
}
}
protected DS store;
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 extends DataSource<DS>> T copy() {
@ -38,14 +78,6 @@ public abstract class DataSource<DS extends DataStore> {
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<DS extends DataStore> {
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;
}

View file

@ -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<DS extends DataStore> extends DataSource<D
@Override
public final DataSourceInfo determineInfo() throws Exception {
if (!getFlow().hasInput()) {
return new DataSourceInfo.Table(null, -1);
}
try (var con = openReadConnection()) {
var dataType = con.getDataType();
var rowCount = con.getRowCount();
return new DataSourceInfo.Table(dataType, rowCount);
return new DataSourceInfo.Table(dataType, rowCount.orElse(-1));
}
}
@ -41,12 +46,22 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
return con;
}
public final TableWriteConnection openPrependingWriteConnection() throws Exception {
var con = newPrependingWriteConnection();
con.init();
return con;
}
protected TableWriteConnection newWriteConnection() {
throw new UnsupportedOperationException();
}
protected TableWriteConnection newAppendingWriteConnection() {
throw new UnsupportedOperationException();
return new PreservingTableWriteConnection(this, newWriteConnection(), true);
}
protected TableWriteConnection newPrependingWriteConnection() {
return new PreservingTableWriteConnection(this, newWriteConnection(), false);
}
protected TableReadConnection newReadConnection() {

View file

@ -10,6 +10,7 @@ import io.xpipe.core.data.typed.TypedDataStreamWriter;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.OptionalInt;
import java.util.concurrent.atomic.AtomicInteger;
/**
@ -25,8 +26,8 @@ public interface TableReadConnection extends DataSourceReadConnection {
}
@Override
public int getRowCount() throws Exception {
return 0;
public OptionalInt getRowCount() throws Exception {
return OptionalInt.empty();
}
@Override
@ -49,7 +50,9 @@ public interface TableReadConnection extends DataSourceReadConnection {
/**
* Returns the amount of rows to be read or -1 if the amount is unknown.
*/
int getRowCount() throws Exception;
default OptionalInt getRowCount() throws Exception {
return OptionalInt.empty();
}
/**
* Consumes the table rows until the acceptor returns false.
@ -59,7 +62,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
/**
* Reads multiple rows in bulk.
*/
default ArrayNode readRows(int maxLines) throws Exception{
default ArrayNode readRows(int maxLines) throws Exception {
var list = new ArrayList<DataStructureNode>();
AtomicInteger rowCounter = new AtomicInteger();

View file

@ -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<DS extends DataStore> extends DataSource<DS
}
@Override
public final TextWriteConnection openPrependingWriteConnection() throws Exception {
var con = newPrependingWriteConnection();
con.init();
return con;
}
protected abstract TextWriteConnection newWriteConnection();
protected abstract TextWriteConnection newAppendingWriteConnection();
protected TextWriteConnection newAppendingWriteConnection() {
return new PreservingTextWriteConnection(this, newWriteConnection(), true);
}
protected TextWriteConnection newPrependingWriteConnection() {
return new PreservingTextWriteConnection(this, newWriteConnection(), false);
}
protected abstract TextReadConnection newReadConnection();
}

View file

@ -0,0 +1,27 @@
package io.xpipe.core.source;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum WriteMode {
@JsonProperty("replace")
REPLACE(DataSource::openWriteConnection),
@JsonProperty("append")
APPEND(DataSource::openAppendingWriteConnection),
@JsonProperty("prepend")
PREPEND(DataSource::openPrependingWriteConnection);
public static interface FailableFunction<T, R, E extends Throwable> {
R apply(T input) throws E;
}
private final FailableFunction<DataSource<?>, DataSourceConnection, Exception> connectionOpener;
WriteMode(FailableFunction<DataSource<?>, DataSourceConnection, Exception> connectionOpener) {
this.connectionOpener = connectionOpener;
}
public DataSourceConnection open(DataSource<?> source) throws Exception {
return connectionOpener.apply(source);
}
}

View file

@ -0,0 +1,23 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum DataFlow {
@JsonProperty("input")
INPUT,
@JsonProperty("output")
OUTPUT,
@JsonProperty("inputOutput")
INPUT_OUTPUT,
@JsonProperty("transformer")
TRANSFORMER;
public boolean hasInput() {
return this == INPUT || this == INPUT_OUTPUT;
}
public boolean hasOutput() {
return this == OUTPUT || this == INPUT_OUTPUT;
}
}

View file

@ -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.

View file

@ -1,8 +0,0 @@
package io.xpipe.core.store;
public enum DataStoreFlow {
INPUT,
OUTPUT,
INOUT
}

View file

@ -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("[\\\\/]");

View file

@ -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";
}
}

View file

@ -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);

View file

@ -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 extends DataStore> DS asNeeded() {
throw new UnsupportedOperationException();

View file

@ -23,6 +23,6 @@ public class OutputStreamStore implements StreamDataStore {
@Override
public boolean canOpen() {
return true;
return false;
}
}

View file

@ -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<String> switchTo(List<String> 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<String> createFileReadCommand(String file) {
return List.of("Get-Content", file);
}
@Override
public List<String> createFileWriteCommand(String file) {
return List.of("Out-File", "-FilePath", file);
}
public static StandardShellStore.ShellType[] getWindowsShells() {
return new StandardShellStore.ShellType[]{
CMD,
POWERSHELL
};
}
@Override
public List<String> 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<String> 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<String> 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<String> switchTo(List<String> cmd) {
var l = new ArrayList<>(cmd);
l.add(0, "powershell.exe");
return l;
}
@Override
public List<String> createFileReadCommand(String file) {
return List.of("Get-Content", file);
}
@Override
public List<String> createFileWriteCommand(String file) {
return List.of("Out-File", "-FilePath", file);
}
@Override
public List<String> createFileExistsCommand(String file) {
return List.of("Test-Path", "-path", file);
}
@Override
public Charset determineCharset(ShellStore store) throws Exception {
return StandardCharsets.UTF_16LE;
}
@Override
public NewLine getNewLine() {
return NewLine.CRLF;
}
@Override
public String getName() {
return "powershell";
}
@Override
public String getDisplayName() {
return "PowerShell";
}
@Override
public List<String> getOperatingSystemNameCommand() {
return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\"");
}
}
@JsonTypeName("sh")
@Value
public static class Sh implements StandardShellStore.ShellType {
@Override
public List<String> switchTo(List<String> 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<String> 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;
}
}

View file

@ -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<String> switchTo(List<String> cmd);
@ -25,7 +26,7 @@ public abstract class StandardShellStore extends ShellStore implements MachineFi
List<String> createFileExistsCommand(String file);
Charset getCharset();
Charset determineCharset(ShellStore store) throws Exception;
NewLine getNewLine();

View file

@ -14,7 +14,7 @@ import java.io.OutputStream;
public class StdinDataStore implements StreamDataStore {
@Override
public boolean isLocalToApplication() {
public boolean isContentExclusivelyAccessible() {
return true;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeNull();
}
}
public static class NullDeserializer extends JsonDeserializer<DataSource> {
@Override
public DataSource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return null;
}
}
public static class DataSourceReferenceSerializer extends JsonSerializer<DataSourceReference> {
@Override

View file

@ -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;

View file

@ -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"

View file

@ -0,0 +1,26 @@
package io.xpipe.extension;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Supplier;
public interface Cache {
Cache INSTANCE = ServiceLoader.load(Cache.class).findFirst().orElseThrow();
public <T> T getValue(String key, Class<?> type, Supplier<T> notPresent);
public static <T> T get(String key, Class<T> type, Supplier<T> notPresent) {
return INSTANCE.getValue(key, type, notPresent);
}
public static <T> Optional<T> getIfPresent(String key, Class<T> type) {
return Optional.ofNullable(get(key, type, () -> null));
}
public <T> void updateValue(String key, T val);
public static <T> void update(String key, T val) {
INSTANCE.updateValue(key, key);
}
}

View file

@ -86,8 +86,8 @@ public interface DataSourceProvider<T extends DataSource<?>> {
Dialog configDialog(T source, boolean all);
default boolean isHidden() {
return false;
default boolean shouldShow(DataSourceType type) {
return type == null || type == getPrimaryType();
}
DataSourceType getPrimaryType();

View file

@ -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<DataSourceProvider<?>> ALL;
private static List<DataSourceProvider<?>> 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<DataSourceProvider<?>> getAll() {
public static List<DataSourceProvider<?>> getAll() {
return ALL;
}
}

View file

@ -77,10 +77,6 @@ public interface DataStoreProvider {
DataStore defaultStore();
default String display(DataStore store) {
return store.toSummaryString();
}
List<String> getPossibleNames();
default String getId() {
@ -89,7 +85,7 @@ public interface DataStoreProvider {
List<Class<?>> getStoreClasses();
default DataStoreFlow[] getPossibleFlows() {
return new DataStoreFlow[]{DataStoreFlow.INPUT};
default DataFlow[] getPossibleFlows() {
return new DataFlow[]{DataFlow.INPUT};
}
}

View file

@ -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<DataStoreProvider> ALL;
private static List<DataStoreProvider> 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<DataStoreProvider> getAll() {
public static List<DataStoreProvider> getAll() {
return ALL;
}
}

View file

@ -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 {
}
}

View file

@ -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;

View file

@ -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<String> resolver(String s, Object... vars) {
return () -> get(s, vars);
}
public static ObservableValue<String> 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);
}

View file

@ -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;
}
}

View file

@ -11,11 +11,13 @@ public interface Translatable {
static <T extends Translatable> StringConverter<T> 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();
}
};

View file

@ -1,40 +0,0 @@
package io.xpipe.extension;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import io.xpipe.fxcomps.Comp;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.image.Image;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Predicate;
public interface XPipeDaemon {
static XPipeDaemon getInstance() {
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
}
List<DataStore> getNamedStores();
public Image image(String file);
Comp<?> streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider);
Comp<?> namedStoreChooser(ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category);
Comp<?> namedSourceChooser(ObservableValue<Predicate<DataSource<?>>> filter, Property<? extends DataSource<?>> selected, DataSourceProvider.Category category);
Comp<?> sourceProviderChooser(Property<DataSourceProvider<?>> provider, DataSourceProvider.Category category);
Optional<DataStore> getNamedStore(String name);
Optional<DataSource<?>> getSource(String id);
Optional<String> getStoreName(DataStore store);
Optional<String> getSourceId(DataSource<?> source);
}

View file

@ -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;

View file

@ -8,6 +8,13 @@ import java.util.stream.Collectors;
public record CodeSnippet(List<CodeSnippet.Line> lines) {
public static final ColorScheme LIGHT_MODE = new ColorScheme(
Color.valueOf("0033B3"),
Color.valueOf("000000"),
Color.valueOf("000000"),
Color.valueOf("067D17")
);
public String toString() {
return getRawString();
}
@ -18,7 +25,11 @@ public record CodeSnippet(List<CodeSnippet.Line> 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<CodeSnippet.Line> lines) {
public static class Builder {
private CodeSnippets.ColorScheme scheme;
private ColorScheme scheme;
private List<Line> lines;
private List<Element> 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<CodeSnippet.Line> lines) {
public static record Line(List<CodeSnippet.Element> elements) {
}
public static record ColorScheme(
Color keyword, Color identifier, Color type, Color string) {
}
}

View file

@ -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) {
}
}

View file

@ -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<DataStoreFlow> selected;
DataStoreFlow[] available;
Property<DataFlow> selected;
DataFlow[] available;
@Override
protected Region createSimple() {
var map = new LinkedHashMap<DataStoreFlow, ObservableValue<String>>();
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<DataFlow, ObservableValue<String>>();
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));

View file

@ -41,6 +41,7 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
for (var entry : getEntries()) {
var line = new HBox();
line.setFillHeight(true);
if (!wrap) {
line.prefWidthProperty().bind(flow.widthProperty());
}

View file

@ -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<S extends CompStructure<?>> implements Augment<S> {
static {
@ -23,8 +21,8 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
private final ObservableValue<String> text;
public FancyTooltipAugment(Supplier<String> text) {
this.text = new SimpleObjectProperty<>(text.get());
public FancyTooltipAugment(ObservableValue<String> text) {
this.text = PlatformThread.sync(text);
}
public FancyTooltipAugment(String key) {
@ -73,8 +71,48 @@ public class FancyTooltipAugment<S extends CompStructure<?>> 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);
}
}
}
}

View file

@ -0,0 +1,4 @@
package io.xpipe.extension.comp;
public class MultiVariantComp {
}

View file

@ -0,0 +1,46 @@
package io.xpipe.extension.comp;
import io.xpipe.core.source.WriteMode;
import io.xpipe.extension.I18n;
import io.xpipe.extension.util.SimpleValidator;
import io.xpipe.extension.util.Validatable;
import io.xpipe.extension.util.Validator;
import io.xpipe.extension.util.Validators;
import io.xpipe.fxcomps.SimpleComp;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region;
import lombok.EqualsAndHashCode;
import lombok.Value;
import net.synedra.validatorfx.Check;
import java.util.LinkedHashMap;
@Value
@EqualsAndHashCode(callSuper = true)
public class WriteModeChoiceComp extends SimpleComp implements Validatable {
Property<WriteMode> selected;
WriteMode[] available;
Validator validator = new SimpleValidator();
Check check;
public WriteModeChoiceComp(Property<WriteMode> selected, WriteMode[] available) {
this.selected = selected;
this.available = available;
check = Validators.nonNull(validator, I18n.observable("mode"), selected);
}
@Override
protected Region createSimple() {
var map = new LinkedHashMap<WriteMode, ObservableValue<String>>();
map.put(WriteMode.REPLACE, I18n.observable("replace"));
map.put(WriteMode.APPEND, I18n.observable("append"));
map.put(WriteMode.PREPEND, I18n.observable("prepend"));
return new ToggleGroupComp<>(selected, map).apply(struc -> {
new FancyTooltipAugment<>("extension.replaceDescription").augment(struc.get().getChildren().get(0));
new FancyTooltipAugment<>("extension.appendDescription").augment(struc.get().getChildren().get(1));
new FancyTooltipAugment<>("extension.prependDescription").augment(struc.get().getChildren().get(2));
}).apply(struc -> check.decorates(struc.get())).createRegion();
}
}

View file

@ -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);
}

View file

@ -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<Validator> validators;
private final ReadOnlyObjectWrapper<ValidationResult> validationResultProperty = new ReadOnlyObjectWrapper<>(new ValidationResult());

View file

@ -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;

View file

@ -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);
}

View file

@ -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<T> {
this.wrap = false;
this.title = title;
}
public DynamicOptionsBuilder<T> addTitle(String titleKey) {
return addTitle(I18n.observable(titleKey));
}
public DynamicOptionsBuilder<T> addTitle(ObservableValue<String> title) {
entries.add(new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
@ -72,7 +74,7 @@ public class DynamicOptionsBuilder<T> {
for (var e : NewLine.values()) {
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<T> {
return this;
}
public DynamicOptionsBuilder<T> addComp(Comp<?> comp) {
return addComp((ObservableValue<String>) null, comp, null);
}
public DynamicOptionsBuilder<T> addComp(Comp<?> comp, Property<?> prop) {
return addComp((ObservableValue<String>) null, comp, prop);
}
public DynamicOptionsBuilder<T> addComp(String nameKey, Comp<?> comp, Property<?> prop) {
return addComp(I18n.observable(nameKey), comp, prop);
}

View file

@ -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<T> implements io.xpipe.extension.Validator {
public final class ExclusiveValidator<T> implements Validator {
private final Map<T, ? extends Validator> validators;
private final ObservableValue<T> obs;

View file

@ -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());
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,76 @@
package io.xpipe.extension.util;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import java.util.Set;
public class PrettyListView<T> extends ListView<T> {
private final ScrollBar vBar = new ScrollBar();
private final ScrollBar hBar = new ScrollBar();
public PrettyListView() {
super();
skinProperty().addListener(it -> {
// first bind, then add new scrollbars, otherwise the new bars will be found
bindScrollBars();
getChildren().addAll(vBar, hBar);
});
getStyleClass().add("jfx-list-view");
vBar.setManaged(false);
vBar.setOrientation(Orientation.VERTICAL);
vBar.getStyleClass().add("pretty-scroll-bar");
// vBar.visibleProperty().bind(vBar.visibleAmountProperty().isNotEqualTo(0));
hBar.setManaged(false);
hBar.setOrientation(Orientation.HORIZONTAL);
hBar.getStyleClass().add("pretty-scroll-bar");
hBar.visibleProperty().setValue(false);
}
private void bindScrollBars() {
final Set<Node> nodes = lookupAll("VirtualScrollBar");
for (Node node : nodes) {
if (node instanceof ScrollBar) {
ScrollBar bar = (ScrollBar) node;
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
bindScrollBars(vBar, bar, true);
} else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
bindScrollBars(hBar, bar, false);
}
}
}
}
private void bindScrollBars(ScrollBar scrollBarA, ScrollBar scrollBarB, boolean bindVisibility) {
scrollBarA.valueProperty().bindBidirectional(scrollBarB.valueProperty());
scrollBarA.minProperty().bindBidirectional(scrollBarB.minProperty());
scrollBarA.maxProperty().bindBidirectional(scrollBarB.maxProperty());
scrollBarA.visibleAmountProperty().bindBidirectional(scrollBarB.visibleAmountProperty());
scrollBarA.unitIncrementProperty().bindBidirectional(scrollBarB.unitIncrementProperty());
scrollBarA.blockIncrementProperty().bindBidirectional(scrollBarB.blockIncrementProperty());
if (bindVisibility) {
scrollBarA.visibleProperty().bind(scrollBarB.visibleProperty());
}
}
@Override
protected void layoutChildren() {
super.layoutChildren();
Insets insets = getInsets();
double w = getWidth();
double h = getHeight();
final double prefWidth = vBar.prefWidth(-1);
vBar.resizeRelocate(w - prefWidth - insets.getRight(), insets.getTop(), prefWidth, h - insets.getTop() - insets.getBottom());
final double prefHeight = hBar.prefHeight(-1);
hBar.resizeRelocate(insets.getLeft(), h - prefHeight - insets.getBottom(), w - insets.getLeft() - insets.getRight(), prefHeight);
}
}

View file

@ -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 <T, V> void bindExclusive(Property<V> selected, Map<V, ? extends Property<T>> map, Property<T> toBind) {
selected.addListener((c,o,n) -> {

View file

@ -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;

View file

@ -1,4 +1,4 @@
package io.xpipe.extension;
package io.xpipe.extension.util;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;

View file

@ -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);
}
}
}
}

View file

@ -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;

View file

@ -0,0 +1,6 @@
package io.xpipe.extension.util;
public interface Validatable {
Validator getValidator();
}

View file

@ -1,4 +1,4 @@
package io.xpipe.extension;
package io.xpipe.extension.util;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;

View file

@ -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;

View file

@ -0,0 +1,47 @@
package io.xpipe.extension.util;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
import io.xpipe.core.store.DataStore;
import io.xpipe.extension.DataSourceProvider;
import io.xpipe.extension.DataStoreProvider;
import io.xpipe.fxcomps.Comp;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.image.Image;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Predicate;
public interface XPipeDaemon {
static XPipeDaemon getInstance() {
return ServiceLoader.load(XPipeDaemon.class).findFirst().orElseThrow();
}
List<DataStore> getNamedStores();
public Image image(String file);
<T extends Comp<?> & Validatable> T streamStoreChooser(Property<DataStore> storeProperty, Property<DataSourceProvider<?>> provider);
<T extends Comp<?> & Validatable> T namedStoreChooser(
ObservableValue<Predicate<DataStore>> filter, Property<? extends DataStore> selected, DataStoreProvider.Category category
);
Comp<?> namedSourceChooser(
ObservableValue<Predicate<DataSource<?>>> filter, Property<? extends DataSource<?>> selected, DataSourceProvider.Category category
);
<T extends Comp<?> & Validatable> T sourceProviderChooser(Property<DataSourceProvider<?>> provider, DataSourceProvider.Category category, DataSourceType filter);
Optional<DataStore> getNamedStore(String name);
Optional<DataSource<?>> getSource(String id);
Optional<String> getStoreName(DataStore store);
Optional<String> getSourceId(DataSource<?> source);
}

View file

@ -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;
}

View file

@ -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
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