Rework data structure nodes, other rework

This commit is contained in:
Christopher Schnick 2022-09-04 04:11:01 +02:00
parent c7606f9f8e
commit 4d168b66c0
38 changed files with 408 additions and 762 deletions

View file

@ -11,8 +11,10 @@ import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.typed.TypedAbstractReader;
import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader;
import io.xpipe.core.source.*;
import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import java.io.InputStream;
import java.util.*;
@ -63,7 +65,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
.ref(DataSourceReference.id(getId())).maxRows(maxRows).build();
con.performInputExchange(req, (QueryTableDataExchange.Response res, InputStream in) -> {
var r = new TypedDataStreamParser(info.getDataType());
r.parseStructures(in, TypedDataStructureNodeReader.immutable(info.getDataType()), nodes::add);
r.parseStructures(in, TypedDataStructureNodeReader.of(info.getDataType()), nodes::add);
});
});
return ArrayNode.of(nodes);
@ -78,7 +80,7 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
private final TypedAbstractReader nodeReader;
{
nodeReader = TypedReusableDataStructureNodeReader.create(info.getDataType());
nodeReader = TypedDataStructureNodeReader.of(info.getDataType());
parser = new TypedDataStreamParser(info.getDataType());
connection = XPipeConnection.open();

View file

@ -21,7 +21,7 @@ public class DataTableAccumulatorTest extends DaemonControl {
var acc = DataTableAccumulator.create(type);
var val = type.convert(
TupleNode.of(List.of(ValueNode.of("val1"), ValueNode.of("val2")))).orElseThrow();
TupleNode.of(List.of(ValueNode.ofValue("val1"), ValueNode.ofValue("val2")))).orElseThrow();
acc.add(val);
var table = acc.finish(":test");

View file

@ -6,6 +6,7 @@ import io.xpipe.core.data.node.ValueNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GenericArrayReader implements GenericAbstractReader {
@ -127,9 +128,9 @@ public class GenericArrayReader implements GenericAbstractReader {
}
@Override
public void onValue(byte[] value, boolean textual) {
public void onValue(byte[] value, Map<Integer, String> metaAttributes) {
if (currentReader != null) {
currentReader.onValue(value, textual);
currentReader.onValue(value, metaAttributes);
return;
}
@ -141,7 +142,7 @@ public class GenericArrayReader implements GenericAbstractReader {
throw new IllegalStateException("Array is full but got another value");
}
put(ValueNode.mutable(value, textual));
put(ValueNode.of(value).tag(metaAttributes));
}
@Override

View file

@ -1,5 +1,7 @@
package io.xpipe.core.data.generic;
import java.util.Map;
public interface GenericDataStreamCallback {
default void onName(String name) {
@ -17,6 +19,7 @@ public interface GenericDataStreamCallback {
default void onTupleEnd() {
}
default void onValue(byte[] value, boolean textual) {
default void onValue(byte[] value, Map<Integer, String> metaAttributes) {
}
}

View file

@ -66,6 +66,7 @@ public class GenericDataStreamParser {
for (int i = 0; i < size; i++) {
parse(in, cb);
}
var attributes = DataStructureNodeIO.parseAttributes(in);
cb.onTupleEnd();
}
@ -75,19 +76,14 @@ public class GenericDataStreamParser {
for (int i = 0; i < size; i++) {
parse(in, cb);
}
var attributes = DataStructureNodeIO.parseAttributes(in);
cb.onArrayEnd();
}
private static void parseValue(InputStream in, GenericDataStreamCallback cb) throws IOException {
var type = in.read();
if (type == DataStructureNodeIO.VALUE_TYPE_NULL) {
cb.onValue(null, false);
return;
}
var textual = type == DataStructureNodeIO.VALUE_TYPE_TEXT;
var size = in.read();
var data = in.readNBytes(size);
cb.onValue(data, textual);
var attributes = DataStructureNodeIO.parseAttributes(in);
cb.onValue(data, attributes);
}
}

View file

@ -41,6 +41,7 @@ public class GenericDataStreamWriter {
writeName(out, tuple.keyNameAt(i));
write(out, tuple.at(i));
}
DataStructureNodeIO.writeAttributes(out, tuple);
}
private static void writeArray(OutputStream out, ArrayNode array) throws IOException {
@ -49,17 +50,13 @@ public class GenericDataStreamWriter {
for (int i = 0; i < array.size(); i++) {
write(out, array.at(i));
}
DataStructureNodeIO.writeAttributes(out, array);
}
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
out.write(DataStructureNodeIO.GENERIC_VALUE_ID);
if (n.isNull()) {
out.write(DataStructureNodeIO.VALUE_TYPE_NULL);
return;
}
out.write(n.isTextual() ? DataStructureNodeIO.VALUE_TYPE_TEXT : DataStructureNodeIO.VALUE_TYPE_BARE);
out.write(n.getRawData().length);
out.write(n.getRawData());
DataStructureNodeIO.writeAttributes(out, n);
}
}

View file

@ -3,6 +3,8 @@ package io.xpipe.core.data.generic;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.ValueNode;
import java.util.Map;
public class GenericDataStructureNodeReader implements GenericDataStreamCallback {
private DataStructureNode node;
@ -78,12 +80,13 @@ public class GenericDataStructureNodeReader implements GenericDataStreamCallback
}
@Override
public void onValue(byte[] value, boolean textual) {
public void onValue(byte[] value, Map<Integer, String> metaAttributes) {
if (hasReader()) {
reader.onValue(value, textual);
reader.onValue(value, metaAttributes);
return;
}
node = ValueNode.mutable(value, textual);
node = ValueNode.of(value).tag(metaAttributes);
}
}

View file

@ -7,6 +7,7 @@ import io.xpipe.core.data.node.ValueNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GenericTupleReader implements GenericAbstractReader {
@ -139,9 +140,9 @@ public class GenericTupleReader implements GenericAbstractReader {
}
@Override
public void onValue(byte[] value, boolean textual) {
public void onValue(byte[] value, Map<Integer, String> metaAttributes) {
if (currentReader != null) {
currentReader.onValue(value, textual);
currentReader.onValue(value, metaAttributes);
return;
}
@ -153,7 +154,7 @@ public class GenericTupleReader implements GenericAbstractReader {
throw new IllegalStateException("Tuple is full but got another value");
}
putNode(ValueNode.mutable(value, textual));
putNode(ValueNode.of(value).tag(metaAttributes));
}
@Override

View file

@ -17,11 +17,7 @@ public abstract class ArrayNode extends DataStructureNode {
}
public static ArrayNode of(List<DataStructureNode> nodes) {
return new SimpleArrayNode(true, nodes);
}
public static ArrayNode of(boolean mutable, List<DataStructureNode> nodes) {
return new SimpleArrayNode(mutable, nodes);
return new SimpleArrayNode(nodes);
}
protected ArrayNode() {
@ -29,8 +25,12 @@ public abstract class ArrayNode extends DataStructureNode {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ArrayNode that)) return false;
if (this == o) {
return true;
}
if (!(o instanceof ArrayNode that)) {
return false;
}
return getNodes().equals(that.getNodes());
}
@ -49,18 +49,10 @@ public abstract class ArrayNode extends DataStructureNode {
return "array node";
}
@Override
public abstract ArrayNode immutableView();
@Override
public abstract ArrayNode mutableCopy();
protected abstract String getIdentifier();
@Override
public final String toString(int indent) {
var content = getNodes().stream().map(n -> n.toString(indent)).collect(Collectors.joining(", "));
return "(" + getIdentifier() + ") [" + content + "]";
return "[" + content + "]";
}
@Override

View file

@ -8,20 +8,70 @@ import java.util.stream.Stream;
public abstract class DataStructureNode implements Iterable<DataStructureNode> {
public static final String KEY_TABLE_NAME = "tableName";
public static final String KEY_ROW_NAME = "rowName";
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;
private Properties properties = new Properties();
private Map<Integer, String> metaAttributes;
public String getMetaString(String key) {
if (properties == null) {
public Map<Integer, String> getMetaAttributes() {
return metaAttributes != null ? Collections.unmodifiableMap(metaAttributes) : null;
}
public DataStructureNode tag(Integer key) {
if (metaAttributes == null) {
metaAttributes = new HashMap<>();
}
metaAttributes.put(key, null);
return this;
}
public DataStructureNode tag(Map<Integer, String> metaAttributes) {
if (metaAttributes == null) {
return this;
}
if (this.metaAttributes == null) {
this.metaAttributes = new HashMap<>();
}
this.metaAttributes.putAll(metaAttributes);
return this;
}
public DataStructureNode tag(Integer key, String value) {
if (metaAttributes == null) {
metaAttributes = new HashMap<>();
}
metaAttributes.put(key, value);
return this;
}
public String getMetaAttribute(Integer key) {
if (metaAttributes == null) {
return null;
}
return properties.getProperty(key);
return metaAttributes.get(key);
}
public abstract DataStructureNode mutableCopy();
public boolean hasMetaAttribute(Integer key) {
if (metaAttributes == null) {
return false;
}
return metaAttributes.containsKey(key);
}
public DataStructureNode mutable() {
return this;
}
public String keyNameAt(int index) {
throw unsupported("key name at");
@ -50,8 +100,6 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
public abstract boolean isMutable();
public abstract DataStructureNode immutableView();
@Override
public String toString() {
return toString(0);
@ -61,26 +109,6 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
throw unsupported("clear");
}
public boolean isTextual() {
throw unsupported("textual check");
}
public DataStructureNode setRaw(byte[] data) {
throw unsupported("set raw data");
}
public DataStructureNode set(Object newValue) {
throw unsupported("set");
}
public DataStructureNode set(Object newValue, boolean textual) {
throw unsupported("set");
}
public DataStructureNode set(int index, DataStructureNode node) {
throw unsupported("set at index");
}
public abstract String toString(int indent);
public boolean isTuple() {
@ -99,6 +127,12 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
return false;
}
public DataStructureNode set(int index, DataStructureNode node) {
throw unsupported("set at index");
}
public final ValueNode asValue() {
if (!isValue()) {
throw new UnsupportedOperationException(getName() + " is not a value node");

View file

@ -1,5 +1,12 @@
package io.xpipe.core.data.node;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class DataStructureNodeIO {
public static final int GENERIC_STRUCTURE_ID = 0;
@ -13,7 +20,47 @@ public class DataStructureNodeIO {
public static final int TYPED_ARRAY_ID = 7;
public static final int TYPED_VALUE_ID = 8;
public static final int VALUE_TYPE_BARE = 0;
public static final int VALUE_TYPE_TEXT = 1;
public static final int VALUE_TYPE_NULL = 2;
public static void writeString(OutputStream out, String s) throws IOException {
if (s != null) {
var b = s.getBytes(StandardCharsets.UTF_8);
out.write(b.length);
out.write(b);
}
}
public static String parseString(InputStream in) throws IOException {
var nameLength = in.read();
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();
if (attributesLength == 0) {
return null;
}
var map = new HashMap<Integer, String>();
for (int i = 0; i < attributesLength; i++) {
var key = in.read();
var value = parseString(in);
map.put(key, value);
}
return map;
}
public static void writeAttributes(OutputStream out, DataStructureNode s) throws IOException {
if (s.getMetaAttributes() != null) {
out.write(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);
out.write(value);
}
} else {
out.write(0);
}
}
}

View file

@ -1,34 +0,0 @@
package io.xpipe.core.data.node;
public abstract class ImmutableValueNode extends ValueNode {
@Override
public String toString(int indent) {
return (isTextual() ? "\"" : "") + asString() + (isTextual() ? "\"" : "") + " (I)";
}
@Override
public boolean isMutable() {
return false;
}
@Override
public ValueNode immutableView() {
return this;
}
@Override
public DataStructureNode setRaw(byte[] data) {
throw new UnsupportedOperationException("Value node is immutable");
}
@Override
public DataStructureNode set(Object newValue) {
throw new UnsupportedOperationException("Value node is immutable");
}
@Override
public DataStructureNode set(Object newValue, boolean textual) {
throw new UnsupportedOperationException("Value node is immutable");
}
}

View file

@ -169,21 +169,11 @@ public class LinkedTupleNode extends TupleNode {
}
@Override
protected String getIdentifier() {
return "linked tuple node";
}
@Override
public TupleNode mutableCopy() {
public TupleNode mutable() {
if (isMutable()) {
return this;
}
return new LinkedTupleNode(tupleNodes.stream().map(n -> n.isMutable() ? n : n.mutableCopy()).toList());
}
@Override
public TupleNode immutableView() {
return this;
return new LinkedTupleNode(tupleNodes.stream().map(n -> n.isMutable() ? n : n.mutable()).toList());
}
}

View file

@ -1,94 +0,0 @@
package io.xpipe.core.data.node;
import java.nio.charset.StandardCharsets;
public class MutableValueNode extends ValueNode {
static final MutableValueNode NULL = new MutableValueNode(null, false);
private byte[] data;
private boolean textual;
MutableValueNode(byte[] data, boolean textual) {
this.data = data;
this.textual = textual;
}
@Override
public String asString() {
return new String(data);
}
@Override
public String toString(int indent) {
if (isNull()) {
return "null (M)";
}
return (textual ? "\"" : "") + new String(data) + (textual ? "\"" : "") + " (M)";
}
@Override
public boolean isNull() {
return data == null;
}
@Override
public boolean isTextual() {
return textual;
}
@Override
public boolean isMutable() {
return true;
}
@Override
public ValueNode immutableView() {
return new SimpleImmutableValueNode(data, textual);
}
@Override
public ValueNode mutableCopy() {
return new MutableValueNode(data, textual);
}
@Override
public DataStructureNode setRaw(byte[] data) {
this.data = data;
return this;
}
@Override
public DataStructureNode set(Object newValue) {
if (newValue == null) {
this.data = null;
this.textual = false;
} else {
setRaw(newValue.toString().getBytes(StandardCharsets.UTF_8));
}
return this;
}
@Override
public DataStructureNode set(Object newValue, boolean textual) {
if (newValue == null && textual) {
throw new IllegalArgumentException("Can't set a textual null");
}
if (newValue == null) {
this.data = null;
this.textual = false;
} else {
setRaw(newValue.toString().getBytes(StandardCharsets.UTF_8));
this.textual = textual;
}
return this;
}
public byte[] getRawData() {
return data;
}
}

View file

@ -1,86 +0,0 @@
package io.xpipe.core.data.node;
import io.xpipe.core.data.type.DataType;
import io.xpipe.core.data.type.TupleType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class NoKeyTupleNode extends TupleNode {
private final boolean mutable;
private final List<DataStructureNode> nodes;
NoKeyTupleNode(boolean mutable, List<DataStructureNode> nodes) {
this.mutable = mutable;
this.nodes = mutable ? nodes : Collections.unmodifiableList(nodes);
}
@Override
public TupleNode mutableCopy() {
return new NoKeyTupleNode(true, nodes.stream()
.map(DataStructureNode::mutableCopy)
.collect(Collectors.toCollection(ArrayList::new)));
}
@Override
public TupleNode immutableView() {
return new NoKeyTupleNode(false, nodes.stream()
.map(DataStructureNode::immutableView)
.collect(Collectors.toCollection(ArrayList::new)));
}
@Override
public DataStructureNode set(int index, DataStructureNode node) {
checkMutable();
nodes.set(index, node);
return this;
}
@Override
public DataType determineDataType() {
return TupleType.of(nodes.stream().map(DataStructureNode::determineDataType).toList());
}
@Override
protected String getName() {
return "no key tuple node";
}
@Override
public boolean isMutable() {
return mutable;
}
@Override
public DataStructureNode at(int index) {
return nodes.get(index);
}
@Override
public int size() {
return nodes.size();
}
@Override
public List<KeyValue> getKeyValuePairs() {
return nodes.stream().map(n -> new KeyValue(null, n)).toList();
}
@Override
public List<String> getKeyNames() {
return Collections.nCopies(size(), null);
}
public List<DataStructureNode> getNodes() {
return nodes;
}
@Override
protected String getIdentifier() {
return "NK";
}
}

View file

@ -1,38 +1,29 @@
package io.xpipe.core.data.node;
import java.util.*;
import lombok.EqualsAndHashCode;
import lombok.Value;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Value
@EqualsAndHashCode(callSuper = true)
public class SimpleArrayNode extends ArrayNode {
private final boolean mutable;
private final List<DataStructureNode> nodes;
SimpleArrayNode(boolean mutable, List<DataStructureNode> nodes) {
this.nodes = nodes;
this.mutable = mutable;
}
private void checkMutable() {
if (!mutable) {
throw new UnsupportedOperationException("Array node is immutable");
}
}
List<DataStructureNode> nodes;
@Override
public DataStructureNode put(DataStructureNode node) {
checkMutable();
nodes.add(node);
return this;
}
@Override
public DataStructureNode set(int index, DataStructureNode node) {
checkMutable();
nodes.add(index, node);
return this;
}
@ -47,34 +38,13 @@ public class SimpleArrayNode extends ArrayNode {
return nodes.size();
}
@Override
public ArrayNode mutableCopy() {
return new SimpleArrayNode(true, nodes.stream()
.map(DataStructureNode::mutableCopy)
.collect(Collectors.toCollection(ArrayList::new)));
}
@Override
protected String getIdentifier() {
return "S";
}
@Override
public boolean isMutable() {
return mutable;
}
@Override
public ArrayNode immutableView() {
return new SimpleArrayNode(false, nodes.stream()
.map(DataStructureNode::immutableView)
.collect(Collectors.toCollection(ArrayList::new)));
return true;
}
@Override
public DataStructureNode clear() {
checkMutable();
nodes.clear();
return this;
}
@ -101,13 +71,11 @@ public class SimpleArrayNode extends ArrayNode {
@Override
public List<DataStructureNode> getNodes() {
return nodes;
return Collections.unmodifiableList(nodes);
}
@Override
public DataStructureNode remove(int index) {
checkMutable();
nodes.remove(index);
return this;
}

View file

@ -1,25 +1,15 @@
package io.xpipe.core.data.node;
import lombok.NonNull;
import java.nio.charset.StandardCharsets;
public class SimpleImmutableValueNode extends ImmutableValueNode {
public class SimpleImmutableValueNode extends ValueNode {
private final byte[] data;
private final boolean textual;
private final byte @NonNull [] data;
SimpleImmutableValueNode(byte[] data, boolean textual) {
SimpleImmutableValueNode(byte @NonNull [] data) {
this.data = data;
this.textual = textual;
}
@Override
public ValueNode mutableCopy() {
return ValueNode.mutable(data, textual);
}
@Override
public boolean isTextual() {
return textual;
}
public byte[] getRawData() {
@ -28,10 +18,20 @@ public class SimpleImmutableValueNode extends ImmutableValueNode {
@Override
public final String asString() {
if (getRawData() == null) {
if (getRawData().length == 0 && !hasMetaAttribute(TEXT)) {
return "null";
}
return new String(getRawData(), StandardCharsets.UTF_8);
}
@Override
public String toString(int indent) {
return (hasMetaAttribute(TEXT) ? "\"" : "") + asString() + (hasMetaAttribute(TEXT) ? "\"" : "") + " (I)";
}
@Override
public boolean isMutable() {
return false;
}
}

View file

@ -3,41 +3,23 @@ package io.xpipe.core.data.node;
import io.xpipe.core.data.type.DataType;
import io.xpipe.core.data.type.TupleType;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class SimpleTupleNode extends TupleNode {
private final boolean mutable;
private final List<String> names;
private final List<DataStructureNode> nodes;
SimpleTupleNode(boolean mutable, List<String> names, List<DataStructureNode> nodes) {
this.mutable = mutable;
this.names = mutable ? names : Collections.unmodifiableList(names);
this.nodes = mutable ? nodes : Collections.unmodifiableList(nodes);
public SimpleTupleNode(List<String> names, List<DataStructureNode> nodes) {
this.names = names;
this.nodes = nodes;
}
@Override
public TupleNode mutableCopy() {
var nodesCopy = nodes.stream()
.map(DataStructureNode::mutableCopy)
.collect(Collectors.toCollection(ArrayList::new));
return new SimpleTupleNode(true, new ArrayList<>(names), nodesCopy);
}
@Override
public TupleNode immutableView() {
var nodesCopy = nodes.stream()
.map(DataStructureNode::immutableView)
.collect(Collectors.toCollection(ArrayList::new));
return new SimpleTupleNode(false, names, nodesCopy);
}
@Override
public DataStructureNode set(int index, DataStructureNode node) {
checkMutable();
nodes.set(index, node);
return this;
}
@ -54,7 +36,7 @@ public class SimpleTupleNode extends TupleNode {
@Override
public boolean isMutable() {
return mutable;
return true;
}
@Override
@ -83,8 +65,6 @@ public class SimpleTupleNode extends TupleNode {
@Override
public DataStructureNode clear() {
checkMutable();
nodes.clear();
names.clear();
return this;
@ -118,7 +98,7 @@ public class SimpleTupleNode extends TupleNode {
}
@Override
protected String getIdentifier() {
return "S";
public TupleNode mutable() {
return this;
}
}

View file

@ -16,7 +16,7 @@ public abstract class TupleNode extends DataStructureNode {
throw new IllegalArgumentException("Nodes must be not null");
}
return new NoKeyTupleNode(true, nodes);
return new SimpleTupleNode(null, nodes);
}
public static TupleNode of(List<String> names, List<DataStructureNode> nodes) {
@ -30,41 +30,20 @@ public abstract class TupleNode extends DataStructureNode {
throw new IllegalArgumentException("Names and nodes must have the same length");
}
return new SimpleTupleNode(true, names, nodes);
return new SimpleTupleNode(names, nodes);
}
public static TupleNode of(boolean mutable, List<String> names, List<DataStructureNode> nodes) {
if (names == null) {
throw new IllegalArgumentException("Names must be not null");
}
if (nodes == null) {
throw new IllegalArgumentException("Nodes must be not null");
}
return new SimpleTupleNode(mutable, names, nodes);
}
@Override
public abstract TupleNode mutable();
public final boolean isTuple() {
return true;
}
protected abstract String getIdentifier();
protected void checkMutable() {
if (!isMutable()) {
throw new UnsupportedOperationException("Tuple node is immutable");
}
}
@Override
public abstract TupleNode mutableCopy();
@Override
public abstract TupleNode immutableView();
@Override
public String toString(int indent) {
var is = " ".repeat(indent);
var start = "(" + getIdentifier() + ") {\n";
var start = "{\n";
var kvs = getKeyValuePairs().stream().map(kv -> {
if (kv.key() == null) {
return is + " " + kv.value().toString(indent + 1) + "\n";

View file

@ -5,17 +5,23 @@ import io.xpipe.core.data.type.ValueType;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
public abstract class ValueNode extends DataStructureNode {
protected ValueNode() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ValueNode that)) return false;
return Arrays.equals(getRawData(), that.getRawData());
if (this == o) {
return true;
}
if (!(o instanceof ValueNode that)) {
return false;
}
return Arrays.equals(getRawData(), that.getRawData()) && Objects.equals(getMetaAttributes(), that.getMetaAttributes());
}
@Override
@ -23,68 +29,18 @@ public abstract class ValueNode extends DataStructureNode {
return Arrays.hashCode(getRawData());
}
@Override
public abstract ValueNode immutableView();
@Override
public abstract ValueNode mutableCopy();
public static ValueNode immutable(byte[] data, boolean textual) {
return new SimpleImmutableValueNode(data, textual);
}
public static ValueNode immutable(Object o, boolean textual) {
if (o == null) {
return immutableNull();
}
return immutable(o.toString().getBytes(StandardCharsets.UTF_8), textual);
}
public static ValueNode immutableNull() {
return MutableValueNode.NULL.immutableView();
}
public static ValueNode mutableNull() {
return MutableValueNode.NULL.mutableCopy();
}
public static ValueNode mutable(byte[] data, boolean textual) {
return new MutableValueNode(data, textual);
}
public static ValueNode mutable(Object o, boolean textual) {
return mutable(o.toString().getBytes(StandardCharsets.UTF_8), textual);
public static ValueNode nullValue() {
return new SimpleImmutableValueNode(new byte[0]);
}
public static ValueNode of(byte[] data) {
return mutable(data, false);
return new SimpleImmutableValueNode(data);
}
public static ValueNode of(Object o) {
return mutable(o, false);
return of(o.toString().getBytes(StandardCharsets.UTF_8));
}
public static ValueNode ofText(byte[] data) {
return mutable(data, true);
}
public static ValueNode ofText(Object o) {
return mutable(o, true);
}
@Override
public abstract boolean isTextual();
@Override
public abstract DataStructureNode setRaw(byte[] data);
@Override
public abstract DataStructureNode set(Object newValue);
@Override
public abstract DataStructureNode set(Object newValue, boolean textual);
@Override
public final int asInt() {
return Integer.parseInt(asString());
@ -107,9 +63,4 @@ public abstract class ValueNode extends DataStructureNode {
public abstract byte[] getRawData();
@Override
public boolean isNull() {
return getRawData() == null;
}
}

View file

@ -5,7 +5,7 @@ import io.xpipe.core.data.type.TupleType;
public interface TypedDataStreamCallback {
default void onValue(byte[] data, boolean textual) {
default void onValue(byte[] data) {
}
default void onGenericNode(DataStructureNode node) {

View file

@ -129,15 +129,8 @@ public class TypedDataStreamParser {
}
private void parseValue(InputStream in, TypedDataStreamCallback cb) throws IOException {
var type = in.read();
if (type == DataStructureNodeIO.VALUE_TYPE_NULL) {
cb.onValue(null, false);
return;
}
var textual = type == DataStructureNodeIO.VALUE_TYPE_TEXT;
var size = in.read();
var data = in.readNBytes(size);
cb.onValue(data, textual);
cb.onValue(data);
}
}

View file

@ -32,12 +32,6 @@ public class TypedDataStreamWriter {
private static void writeValue(OutputStream out, ValueNode n) throws IOException {
out.write(DataStructureNodeIO.TYPED_VALUE_ID);
if (n.isNull()) {
out.write(DataStructureNodeIO.VALUE_TYPE_NULL);
return;
}
out.write(n.isTextual() ? DataStructureNodeIO.VALUE_TYPE_TEXT : DataStructureNodeIO.VALUE_TYPE_BARE);
out.write(n.getRawData().length);
out.write(n.getRawData());
}

View file

@ -6,21 +6,15 @@ import io.xpipe.core.data.type.DataTypeVisitors;
import io.xpipe.core.data.type.TupleType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
public class TypedDataStructureNodeReader implements TypedAbstractReader {
public static TypedDataStructureNodeReader mutable(DataType type) {
return new TypedDataStructureNodeReader(type, false);
public static TypedDataStructureNodeReader of(DataType type) {
return new TypedDataStructureNodeReader(type);
}
public static TypedDataStructureNodeReader immutable(DataType type) {
return new TypedDataStructureNodeReader(type, true);
}
private final boolean makeImmutable;
private DataStructureNode readNode;
private final Stack<List<DataStructureNode>> children;
@ -31,12 +25,11 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
private DataType expectedType;
private int currentExpectedTypeIndex;
private TypedDataStructureNodeReader(DataType type, boolean makeImmutable) {
private TypedDataStructureNodeReader(DataType type) {
flattened = new ArrayList<>();
type.visit(DataTypeVisitors.flatten(flattened::add));
children = new Stack<>();
nodes = new Stack<>();
this.makeImmutable = makeImmutable;
expectedType = flattened.get(0);
}
@ -81,12 +74,12 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
}
@Override
public void onValue(byte[] data, boolean textual) {
public void onValue(byte[] data) {
if (!expectedType.isValue()) {
throw new IllegalStateException("Expected " + expectedType.getName() + " but got value");
}
var val = makeImmutable ? ValueNode.immutable(data, textual) : ValueNode.mutable(data, textual);
var val = ValueNode.of(data);
finishNode(val);
moveExpectedType(false);
}
@ -101,7 +94,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
throw new IllegalStateException("Expected " + expectedType.getName() + " but got generic node");
}
finishNode(makeImmutable ? node.immutableView() : node);
finishNode(node);
moveExpectedType(false);
}
@ -117,10 +110,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
var l = new ArrayList<DataStructureNode>(tupleType.getSize());
children.push(l);
var tupleNames = makeImmutable ?
Collections.unmodifiableList(tupleType.getNames()) : new ArrayList<>(tupleType.getNames());
var tupleNodes = makeImmutable ? Collections.unmodifiableList(l) : l;
var newNode = TupleNode.of(!makeImmutable, tupleNames, tupleNodes);
var newNode = new SimpleTupleNode(tupleType.getNames(), l);
nodes.push(newNode);
}
@ -159,8 +149,7 @@ public class TypedDataStructureNodeReader implements TypedAbstractReader {
var l = new ArrayList<DataStructureNode>();
children.push(l);
var arrayNodes = makeImmutable ? Collections.unmodifiableList(l) : l;
var newNode = ArrayNode.of(arrayNodes);
var newNode = ArrayNode.of(l);
nodes.push(newNode);
}

View file

@ -1,171 +0,0 @@
package io.xpipe.core.data.typed;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.ValueNode;
import io.xpipe.core.data.type.DataType;
import io.xpipe.core.data.type.DataTypeVisitors;
import io.xpipe.core.data.type.TupleType;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class TypedReusableDataStructureNodeReader implements TypedAbstractReader {
public static TypedReusableDataStructureNodeReader create(DataType type) {
return new TypedReusableDataStructureNodeReader(type);
}
private final List<DataType> flattened;
private final TypedDataStructureNodeReader initialReader;
private DataStructureNode node;
private final Stack<Integer> indices;
private int arrayDepth;
private TypedReusableDataStructureNodeReader(DataType type) {
flattened = new ArrayList<>();
indices = new Stack<>();
initialReader = TypedDataStructureNodeReader.mutable(type);
type.visit(DataTypeVisitors.flatten(d -> flattened.add(d)));
}
@Override
public boolean isDone() {
return true;
}
public DataStructureNode create() {
return node;
}
private boolean isInArray() {
return arrayDepth >= 1;
}
private boolean initialized() {
return node != null;
}
@Override
public void onValue(byte[] data, boolean textual) {
if (!initialized()) {
initialReader.onValue(data, textual);
return;
}
if (isInArray()) {
getCurrentParent().set(indices.peek(), ValueNode.mutable(data, textual));
} else {
getCurrent().setRaw(data);
}
if (!indices.isEmpty()) {
indices.push(indices.pop() + 1);
}
}
@Override
public void onGenericNode(DataStructureNode node) {
if (!initialized()) {
initialReader.onGenericNode(node);
return;
}
if (hasParent()) {
getCurrentParent().set(indices.peek(), node);
} else {
this.node = node;
}
if (!indices.isEmpty()) {
indices.push(indices.pop() + 1);
}
}
private boolean hasParent() {
return indices.size() > 0;
}
private DataStructureNode getCurrentParent() {
if (!hasParent()) {
throw new IllegalStateException("No parent available");
}
var current = node;
for (var index : indices.subList(0, indices.size() - 1)) {
current = current.at(index);
}
return current;
}
private DataStructureNode getCurrent() {
var current = node;
for (var index : indices) {
current = current.at(index);
}
return current;
}
@Override
public void onTupleBegin(TupleType type) {
if (!initialized()) {
initialReader.onTupleBegin(type);
return;
}
indices.push(0);
}
@Override
public void onTupleEnd() {
if (!initialized()) {
initialReader.onTupleEnd();
return;
}
indices.pop();
if (!indices.isEmpty()) {
indices.push(indices.pop() + 1);
}
}
@Override
public void onArrayBegin(int size) {
if (!initialized()) {
initialReader.onArrayBegin(size);
return;
}
getCurrent().clear();
indices.push(0);
arrayDepth++;
}
@Override
public void onArrayEnd() {
if (!initialized()) {
initialReader.onArrayEnd();
return;
}
indices.pop();
arrayDepth--;
if (!indices.isEmpty()) {
indices.push(indices.pop() + 1);
}
}
@Override
public void onNodeBegin() {
if (!initialized()) {
initialReader.onNodeBegin();
}
}
@Override
public void onNodeEnd() {
if (!initialized()) {
initialReader.onNodeEnd();
node = initialReader.create();
}
}
}

View file

@ -16,6 +16,9 @@ import java.util.Optional;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface DataStore {
default DataStoreFlow getFlow() {
return DataStoreFlow.INOUT;
}
/**
* Checks whether this store can be opened.

View file

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

View file

@ -124,7 +124,8 @@ public class ShellTypes {
@Override
public Charset getCharset() {
return StandardCharsets.UTF_16LE;
// TODO
return StandardCharsets.ISO_8859_1;
}
@Override

View file

@ -1,16 +1,15 @@
package io.xpipe.core.test;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.generic.GenericDataStreamParser;
import io.xpipe.core.data.generic.GenericDataStreamWriter;
import io.xpipe.core.data.generic.GenericDataStructureNodeReader;
import io.xpipe.core.data.node.ArrayNode;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.TupleNode;
import io.xpipe.core.data.node.ValueNode;
import io.xpipe.core.data.typed.TypedDataStreamParser;
import io.xpipe.core.data.typed.TypedDataStreamWriter;
import io.xpipe.core.data.typed.TypedDataStructureNodeReader;
import io.xpipe.core.data.typed.TypedReusableDataStructureNodeReader;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@ -19,7 +18,6 @@ import org.junit.jupiter.params.provider.EnumSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class DataStructureTest {
@ -91,74 +89,20 @@ public class DataStructureTest {
@ParameterizedTest
@EnumSource(DataStructureTests.TypedDataset.class)
public void testMutableTypedIo(DataStructureTests.TypedDataset ds) throws IOException {
public void testTypedIo(DataStructureTests.TypedDataset ds) throws IOException {
for (var node : ds.nodes) {
var dataOut = new ByteArrayOutputStream();
TypedDataStreamWriter.writeStructure(dataOut, node, ds.type);
var data = dataOut.toByteArray();
var reader = TypedDataStructureNodeReader.mutable(ds.type);
var reader = TypedDataStructureNodeReader.of(ds.type);
new TypedDataStreamParser(ds.type).parseStructure(new ByteArrayInputStream(data), reader);
var readNode = reader.create();
Assertions.assertEquals(node, readNode);
Assertions.assertDoesNotThrow(() -> {
if (readNode.isTuple()) {
readNode.clear();
Assertions.assertEquals(readNode.size(), 0);
}
if (readNode.isArray()) {
readNode.clear();
Assertions.assertEquals(readNode.size(), 0);
}
});
}
}
@ParameterizedTest
@EnumSource(DataStructureTests.TypedDataset.class)
public void testImmutableTypedIo(DataStructureTests.TypedDataset ds) throws IOException {
for (var node : ds.nodes) {
var dataOut = new ByteArrayOutputStream();
TypedDataStreamWriter.writeStructure(dataOut, node, ds.type);
var data = dataOut.toByteArray();
var reader = TypedDataStructureNodeReader.immutable(ds.type);
new TypedDataStreamParser(ds.type).parseStructure(new ByteArrayInputStream(data), reader);
var readNode = reader.create();
Assertions.assertEquals(node, readNode);
Assertions.assertThrows(UnsupportedOperationException.class, () -> {
if (readNode.isTuple() || readNode.isArray()) {
readNode.clear();
Assertions.assertEquals(readNode.size(), 0);
} else {
readNode.setRaw("abc".getBytes(StandardCharsets.UTF_8));
}
});
if (readNode.isTuple() || readNode.isArray()) {
Assertions.assertEquals(readNode.size(), node.size());
}
}
}
@ParameterizedTest
@EnumSource(DataStructureTests.TypedDataset.class)
public void testReusableTypedIo(DataStructureTests.TypedDataset ds) throws IOException {
var dataOut = new ByteArrayOutputStream();
for (var node : ds.nodes) {
TypedDataStreamWriter.writeStructure(dataOut, node, ds.type);
}
var data = dataOut.toByteArray();
var in = new ByteArrayInputStream(data);
var reader = TypedReusableDataStructureNodeReader.create(ds.type);
for (var node : ds.nodes) {
new TypedDataStreamParser(ds.type).parseStructure(in, reader);
var readNode = reader.create();
Assertions.assertEquals(node, readNode);
}
}
}

View file

@ -58,7 +58,7 @@ public class DataStructureTests {
}
private static DataStructureNode createTestData12() {
var val = ValueNode.of(null);
var val = ValueNode.nullValue();
var flatArray = ArrayNode.of();
var flatTuple = TupleNode.builder().add("key1", val).build();
var nestedArray = ArrayNode.of(List.of(flatArray, flatTuple));
@ -101,7 +101,7 @@ public class DataStructureTests {
public static DataStructureNode createTestData32() {
var val = ValueNode.of("value2".getBytes(StandardCharsets.UTF_8));
var flatTuple = TupleNode.builder().add("key1", ValueNode.of(null)).add("key2", ValueNode.of(null)).build();
var flatTuple = TupleNode.builder().add("key1", ValueNode.nullValue()).add("key2", ValueNode.nullValue()).build();
var flatArray = ArrayNode.of(List.of(val, flatTuple));
return flatArray;
}
@ -112,13 +112,13 @@ public class DataStructureTests {
}
public static DataStructureNode createTestData42() {
var val = ValueNode.of(null);
var val = ValueNode.nullValue();
return val;
}
public static DataStructureNode createTestData51() {
var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8));
var flatArray = ArrayNode.of(List.of(val, ValueNode.of(null)));
var flatArray = ArrayNode.of(List.of(val, ValueNode.nullValue()));
var array1 = ArrayNode.of(List.of(flatArray));
var array2 = ArrayNode.of(List.of(array1, array1));
return array2;
@ -143,7 +143,7 @@ public class DataStructureTests {
public static DataStructureNode createTestData61() {
var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8));
var array = ArrayNode.of(List.of(val, ValueNode.of(null)));
var array = ArrayNode.of(List.of(val, ValueNode.nullValue()));
var tuple = TupleNode.builder()
.add(val).add("key2", array).build();
return tuple;
@ -176,7 +176,7 @@ public class DataStructureTests {
public static DataStructureNode createTestData73() {
var val = ValueNode.of("value".getBytes(StandardCharsets.UTF_8));
var array = ArrayNode.of(List.of(val, ValueNode.of(null)));
var array = ArrayNode.of(List.of(val, ValueNode.nullValue()));
return array;
}

View file

@ -1,10 +1,7 @@
package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.MachineFileStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.store.*;
import javafx.beans.property.Property;
import java.util.List;
@ -91,4 +88,8 @@ public interface DataStoreProvider {
}
List<Class<?>> getStoreClasses();
default DataStoreFlow[] getPossibleFlows() {
return new DataStoreFlow[]{DataStoreFlow.INPUT};
}
}

View file

@ -4,13 +4,13 @@ import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.dialog.QueryConverter;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.store.MachineFileStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.*;
import io.xpipe.core.util.Secret;
import lombok.Value;
import java.util.function.Predicate;
public class DialogHelper {
@Value
@ -50,6 +50,10 @@ public class DialogHelper {
});
}
public static Dialog dataStoreFlowQuery(DataStoreFlow flow, DataStoreFlow[] available) {
return Dialog.choice("flow", o -> o.toString(), true, flow, available);
}
public static Dialog shellQuery(DataStore store) {
var storeName = XPipeDaemon.getInstance()
.getStoreName(store)
@ -111,6 +115,22 @@ public class DialogHelper {
});
}
public static Dialog sourceQuery(DataSource<?> source, Predicate<DataSource<?>> filter) {
var id = XPipeDaemon.getInstance()
.getSourceId(source)
.orElse(null);
return Dialog.query("Source Id", false, true, false, id, QueryConverter.STRING)
.map((String newName) -> {
var found = XPipeDaemon.getInstance()
.getSource(newName)
.orElseThrow();
if (!filter.test(found)) {
throw new IllegalArgumentException("Incompatible store type");
}
return found;
});
}
public static Dialog passwordQuery(Secret password) {
return Dialog.querySecret("Password", false, true, password);
}

View file

@ -53,5 +53,9 @@ public interface SupportedApplicationProvider {
default String getGraphicIcon() {
return null;
}
default boolean isApplicable(DataSource<?> source) {
return true;
}
}

View file

@ -1,5 +1,6 @@
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;
@ -25,9 +26,15 @@ public interface XPipeDaemon {
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

@ -0,0 +1,33 @@
package io.xpipe.extension.comp;
import io.xpipe.core.store.DataStoreFlow;
import io.xpipe.extension.I18n;
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 java.util.LinkedHashMap;
@Value
@EqualsAndHashCode(callSuper = true)
public class DataStoreFlowChoiceComp extends SimpleComp {
Property<DataStoreFlow> selected;
DataStoreFlow[] 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"));
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));
new FancyTooltipAugment<>("extension.inoutDescription").augment(struc.get().getChildren().get(2));
}).createRegion();
}
}

View file

@ -149,6 +149,10 @@ public class DynamicOptionsBuilder<T> {
return this;
}
public DynamicOptionsBuilder<T> addComp(String nameKey, Comp<?> comp, Property<?> prop) {
return addComp(I18n.observable(nameKey), comp, prop);
}
public DynamicOptionsBuilder<T> addComp(ObservableValue<String> name, Comp<?> comp, Property<?> prop) {
entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop);

View file

@ -0,0 +1,80 @@
package io.xpipe.extension.comp;
import com.jfoenix.controls.JFXTooltip;
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 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 {
JFXTooltip.setHoverDelay(Duration.millis(400));
JFXTooltip.setVisibleDuration(Duration.INDEFINITE);
}
private final ObservableValue<String> text;
public FancyTooltipAugment(Supplier<String> text) {
this.text = new SimpleObjectProperty<>(text.get());
}
public FancyTooltipAugment(String key) {
this.text = I18n.observable(key);
}
@Override
public void augment(S struc) {
var tt = new FocusTooltip();
var toDisplay = text.getValue();
if (Shortcuts.getShortcut(struc.get()) != null) {
toDisplay = toDisplay + " (" + Shortcuts.getShortcut(struc.get()).getDisplayText() + ")";
}
tt.textProperty().setValue(toDisplay);
tt.setStyle("-fx-font-size: 11pt;");
JFXTooltip.install(struc.get(), tt);
tt.setWrapText(true);
tt.setMaxWidth(400);
tt.getStyleClass().add("fancy-tooltip");
}
public void augment(Node region) {
var tt = new FocusTooltip();
var toDisplay = text.getValue();
if (Shortcuts.getShortcut((Region) region) != null) {
toDisplay = toDisplay + " (" + Shortcuts.getShortcut((Region) region).getDisplayText() + ")";
}
tt.textProperty().setValue(toDisplay);
tt.setStyle("-fx-font-size: 11pt;");
JFXTooltip.install(region, tt);
tt.setWrapText(true);
tt.setMaxWidth(400);
tt.getStyleClass().add("fancy-tooltip");
}
private static class FocusTooltip extends JFXTooltip {
public FocusTooltip() {
}
public FocusTooltip(String string) {
super(string);
}
@Override
protected void show() {
Window owner = getOwnerWindow();
if (owner.isFocused())
super.show();
}
}
}

View file

@ -9,4 +9,10 @@ null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$
namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available
noInformationAvailable=No information available
input=Input
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