mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 00:50:31 +00:00
Implement more data types and various fixes
This commit is contained in:
parent
2cb3670b5b
commit
17feaf8a51
25 changed files with 306 additions and 75 deletions
|
@ -71,8 +71,9 @@ public class DataTableImpl extends DataSourceImpl implements DataTable {
|
|||
.maxRows(maxRows)
|
||||
.build();
|
||||
con.performInputExchange(req, (QueryTableDataExchange.Response res, InputStream in) -> {
|
||||
var r = new TypedDataStreamParser(info.getDataType());
|
||||
r.parseStructures(in, TypedDataStructureNodeReader.of(info.getDataType()), nodes::add);
|
||||
var r = new TypedDataStreamParser(res.getDataType());
|
||||
|
||||
r.parseStructures(in, TypedDataStructureNodeReader.of(res.getDataType()), nodes::add);
|
||||
});
|
||||
});
|
||||
return ArrayNode.of(nodes);
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.beacon.exchange.api;
|
|||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.beacon.exchange.MessageExchange;
|
||||
import io.xpipe.core.data.type.TupleType;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
|
@ -32,5 +33,7 @@ public class QueryTableDataExchange implements MessageExchange {
|
|||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response implements ResponseMessage {}
|
||||
public static class Response implements ResponseMessage {
|
||||
@NonNull TupleType dataType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,18 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
|||
public static final Integer INTEGER_VALUE = 6;
|
||||
public static final Integer IS_NULL = 7;
|
||||
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 IS_DECIMAL = 10;
|
||||
public static final Integer DECIMAL_VALUE = 11;
|
||||
public static final Integer IS_TEXT = 12;
|
||||
public static final Integer IS_INSTANT = 13;
|
||||
public static final Integer IS_BINARY = 14;
|
||||
|
||||
public static final Integer IS_DATE = 15;
|
||||
public static final Integer DATE_VALUE = 16;
|
||||
|
||||
public static final Integer IS_CURRENCY = 17;
|
||||
public static final Integer CURRENCY_CODE = 18;
|
||||
|
||||
private Map<Integer, String> metaAttributes;
|
||||
|
||||
public void clearMetaAttributes() {
|
||||
|
@ -58,12 +64,12 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DataStructureNode tag(Integer key, String value) {
|
||||
public DataStructureNode tag(Integer key, Object value) {
|
||||
if (metaAttributes == null) {
|
||||
metaAttributes = new HashMap<>();
|
||||
}
|
||||
|
||||
metaAttributes.put(key, value);
|
||||
metaAttributes.put(key, value.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -124,6 +130,7 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
|
|||
return "("
|
||||
+ (metaAttributes != null
|
||||
? metaAttributes.entrySet().stream()
|
||||
.sorted(Comparator.comparingInt(entry -> entry.getKey()))
|
||||
.map(e -> e.getValue() != null
|
||||
? e.getKey() + ":" + e.getValue()
|
||||
: e.getKey().toString())
|
||||
|
|
|
@ -6,7 +6,9 @@ import io.xpipe.core.data.type.ValueType;
|
|||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Currency;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class ValueNode extends DataStructureNode {
|
||||
|
@ -25,6 +27,42 @@ public abstract class ValueNode extends DataStructureNode {
|
|||
return new SimpleValueNode(data);
|
||||
}
|
||||
|
||||
public static ValueNode ofDate(String raw, Instant instant) {
|
||||
var created = of(raw);
|
||||
created.tag(IS_DATE);
|
||||
created.tag(DATE_VALUE, instant.toString());
|
||||
return created;
|
||||
}
|
||||
|
||||
public static ValueNode ofDecimal(String raw, double decimal) {
|
||||
return ofDecimal(raw, String.valueOf(decimal));
|
||||
}
|
||||
|
||||
public static ValueNode ofDecimal(String raw, String decimal) {
|
||||
var created = of(raw);
|
||||
created.tag(IS_DECIMAL);
|
||||
created.tag(DECIMAL_VALUE, decimal);
|
||||
return created;
|
||||
}
|
||||
|
||||
public static ValueNode ofInteger(String raw, long integer) {
|
||||
return ofInteger(raw, String.valueOf(integer));
|
||||
}
|
||||
|
||||
public static ValueNode ofInteger(String raw, String integer) {
|
||||
var created = of(raw);
|
||||
created.tag(IS_INTEGER);
|
||||
created.tag(INTEGER_VALUE, integer);
|
||||
return created;
|
||||
}
|
||||
|
||||
public static ValueNode ofCurrency(String raw, String decimal, Currency currency) {
|
||||
var created = ofDecimal(raw, decimal);
|
||||
created.tag(IS_CURRENCY);
|
||||
created.tag(CURRENCY_CODE, currency.getCurrencyCode());
|
||||
return created;
|
||||
}
|
||||
|
||||
public static ValueNode ofBytes(byte[] data) {
|
||||
var created = of(data);
|
||||
created.tag(IS_BINARY);
|
||||
|
@ -49,9 +87,15 @@ public abstract class ValueNode extends DataStructureNode {
|
|||
return created;
|
||||
}
|
||||
|
||||
public static ValueNode ofDecimal(double decimal) {
|
||||
var created = of(decimal);
|
||||
created.tag(IS_DECIMAL);
|
||||
return created;
|
||||
}
|
||||
|
||||
public static ValueNode ofDecimal(BigDecimal decimal) {
|
||||
var created = of(decimal);
|
||||
created.tag(IS_FLOATING_POINT);
|
||||
created.tag(IS_DECIMAL);
|
||||
return created;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.core.dialog;
|
|||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -10,16 +11,18 @@ import java.util.List;
|
|||
@JsonTypeName("choice")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString
|
||||
@Getter
|
||||
public class ChoiceElement extends DialogElement {
|
||||
|
||||
private final String description;
|
||||
private final List<Choice> elements;
|
||||
private final boolean required;
|
||||
private final boolean quiet;
|
||||
|
||||
private int selected;
|
||||
|
||||
@JsonCreator
|
||||
public ChoiceElement(String description, List<Choice> elements, boolean required, int selected) {
|
||||
public ChoiceElement(String description, List<Choice> elements, boolean required, boolean quiet, int selected) {
|
||||
if (elements.stream().allMatch(Choice::isDisabled)) {
|
||||
throw new IllegalArgumentException("All choices are disabled");
|
||||
}
|
||||
|
@ -28,6 +31,7 @@ public class ChoiceElement extends DialogElement {
|
|||
this.elements = elements;
|
||||
this.required = required;
|
||||
this.selected = selected;
|
||||
this.quiet = quiet;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package io.xpipe.core.dialog;
|
||||
|
||||
import io.xpipe.core.charsetter.Charsetter;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -22,11 +22,11 @@ import java.util.function.Supplier;
|
|||
* The evaluation function can be set with {@link #evaluateTo(Supplier)}.
|
||||
* Alternatively, a dialogue can also copy the evaluation function of another dialogue with {@link #evaluateTo(Dialog)}.
|
||||
* An evaluation result can also be mapped to another type with {@link #map(Function)}.
|
||||
* It is also possible to listen for the completion of this dialogue with {@link #onCompletion(Consumer)}.
|
||||
* It is also possible to listen for the completion of this dialogue with {@link #onCompletion(Charsetter.FailableConsumer)} )}.
|
||||
*/
|
||||
public abstract class Dialog {
|
||||
|
||||
private final List<Consumer<?>> completion = new ArrayList<>();
|
||||
private final List<Charsetter.FailableConsumer<?, Exception>> completion = new ArrayList<>();
|
||||
protected Object eval;
|
||||
private Supplier<?> evaluation;
|
||||
|
||||
|
@ -65,8 +65,8 @@ public abstract class Dialog {
|
|||
* @param selected the selected element index
|
||||
*/
|
||||
public static Dialog.Choice choice(
|
||||
String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
|
||||
Dialog.Choice c = new Dialog.Choice(description, elements, required, selected);
|
||||
String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, boolean quiet, int selected) {
|
||||
Dialog.Choice c = new Dialog.Choice(description, elements, required, quiet, selected);
|
||||
c.evaluateTo(c::getSelected);
|
||||
return c;
|
||||
}
|
||||
|
@ -77,12 +77,13 @@ public abstract class Dialog {
|
|||
* @param description the shown question description
|
||||
* @param toString a function that maps the objects to a string
|
||||
* @param required signals whether choices required or can be left empty
|
||||
* @param quiet
|
||||
* @param def the element which is selected by default
|
||||
* @param vals the range of possible elements
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Dialog.Choice choice(
|
||||
String description, Function<T, String> toString, boolean required, T def, T... vals) {
|
||||
String description, Function<T, String> toString, boolean required, boolean quiet, T def, T... vals) {
|
||||
var elements = Arrays.stream(vals)
|
||||
.map(v -> new io.xpipe.core.dialog.Choice(null, toString.apply(v)))
|
||||
.toList();
|
||||
|
@ -91,7 +92,7 @@ public abstract class Dialog {
|
|||
throw new IllegalArgumentException("Default value " + def.toString() + " is not in possible values");
|
||||
}
|
||||
|
||||
var c = choice(description, elements, required, index);
|
||||
var c = choice(description, elements, required, quiet, index);
|
||||
c.evaluateTo(() -> {
|
||||
if (c.getSelected() == -1) {
|
||||
return null;
|
||||
|
@ -249,6 +250,9 @@ public abstract class Dialog {
|
|||
dialog = d.get();
|
||||
var start = dialog.start();
|
||||
evaluateTo(dialog);
|
||||
if (start == null) {
|
||||
complete();
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
|
@ -354,7 +358,7 @@ public abstract class Dialog {
|
|||
boolean required,
|
||||
int selected,
|
||||
Function<Integer, Dialog> c) {
|
||||
var choice = new ChoiceElement(description, elements, required, selected);
|
||||
var choice = new ChoiceElement(description, elements, required, false, selected);
|
||||
return new Dialog() {
|
||||
|
||||
private Dialog choiceMade;
|
||||
|
@ -409,7 +413,7 @@ public abstract class Dialog {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Dialog onCompletion(Consumer<?> s) {
|
||||
public Dialog onCompletion(Charsetter.FailableConsumer<?, Exception> s) {
|
||||
completion.add(s);
|
||||
return this;
|
||||
}
|
||||
|
@ -419,7 +423,7 @@ public abstract class Dialog {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Dialog onCompletion(List<Consumer<?>> s) {
|
||||
public Dialog onCompletion(List<Charsetter.FailableConsumer<?, Exception>> s) {
|
||||
completion.addAll(s);
|
||||
return this;
|
||||
}
|
||||
|
@ -430,13 +434,13 @@ public abstract class Dialog {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> void complete() {
|
||||
public <T> void complete() throws Exception {
|
||||
if (evaluation != null) {
|
||||
eval = evaluation.get();
|
||||
completion.forEach(c -> {
|
||||
Consumer<T> ct = (Consumer<T>) c;
|
||||
for (Charsetter.FailableConsumer<?, Exception> c : completion) {
|
||||
Charsetter.FailableConsumer<T, Exception> ct = (Charsetter.FailableConsumer<T, Exception>) c;
|
||||
ct.accept((T) eval);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -459,8 +463,8 @@ public abstract class Dialog {
|
|||
|
||||
private final ChoiceElement element;
|
||||
|
||||
private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, int selected) {
|
||||
this.element = new ChoiceElement(description, elements, required, selected);
|
||||
private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, boolean required, boolean quiet, int selected) {
|
||||
this.element = new ChoiceElement(description, elements, required, quiet, selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.core.data.type.TupleType;
|
|||
import io.xpipe.core.source.TableReadConnection;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class BufferedTableReadConnection implements TableReadConnection {
|
||||
|
||||
|
@ -50,18 +49,14 @@ public class BufferedTableReadConnection implements TableReadConnection {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
AtomicInteger localCounter = new AtomicInteger();
|
||||
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
TupleNode node;
|
||||
while (((node = get()) != null)) {
|
||||
var returned = lineAcceptor.accept(node);
|
||||
if (!returned) {
|
||||
break;
|
||||
}
|
||||
|
||||
localCounter.getAndIncrement();
|
||||
}
|
||||
return localCounter.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.core.data.type.TupleType;
|
|||
import io.xpipe.core.source.TableReadConnection;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class LimitTableReadConnection implements TableReadConnection {
|
||||
|
||||
|
@ -40,20 +39,15 @@ public class LimitTableReadConnection implements TableReadConnection {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
AtomicInteger localCounter = new AtomicInteger();
|
||||
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
connection.withRows(node -> {
|
||||
if (count == maxCount) {
|
||||
return false;
|
||||
}
|
||||
count++;
|
||||
|
||||
var returned = lineAcceptor.accept(node);
|
||||
localCounter.getAndIncrement();
|
||||
|
||||
return returned;
|
||||
return lineAcceptor.accept(node);
|
||||
});
|
||||
return localCounter.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -60,15 +60,14 @@ public class XpbtReadConnection extends StreamReadConnection implements TableRea
|
|||
}
|
||||
|
||||
@Override
|
||||
public int withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
if (empty) {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = TypedDataStructureNodeReader.of(dataType);
|
||||
AtomicBoolean quit = new AtomicBoolean(false);
|
||||
AtomicReference<Exception> exception = new AtomicReference<>();
|
||||
var counter = 0;
|
||||
while (!quit.get()) {
|
||||
var node = parser.parseStructure(inputStream, reader);
|
||||
if (node == null) {
|
||||
|
@ -80,7 +79,6 @@ public class XpbtReadConnection extends StreamReadConnection implements TableRea
|
|||
if (!lineAcceptor.accept(node.asTuple())) {
|
||||
quit.set(true);
|
||||
}
|
||||
counter++;
|
||||
} catch (Exception ex) {
|
||||
quit.set(true);
|
||||
exception.set(ex);
|
||||
|
@ -90,7 +88,6 @@ public class XpbtReadConnection extends StreamReadConnection implements TableRea
|
|||
if (exception.get() != null) {
|
||||
throw exception.get();
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -58,6 +58,10 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
|
|||
store.checkComplete();
|
||||
}
|
||||
|
||||
public void validate() throws Exception {
|
||||
store.validate();
|
||||
}
|
||||
|
||||
public List<WriteMode> getAvailableWriteModes() {
|
||||
if (getFlow() != null && !getFlow().hasOutput()) {
|
||||
return List.of();
|
||||
|
|
|
@ -25,7 +25,13 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
|
|||
|
||||
@Override
|
||||
public final DataSourceInfo determineInfo() throws Exception {
|
||||
if (!getFlow().hasInput()) {
|
||||
if (!getFlow().hasInput() || !getStore().canOpen()) {
|
||||
return new DataSourceInfo.Table(null, -1);
|
||||
}
|
||||
|
||||
try {
|
||||
checkComplete();
|
||||
} catch (Exception e) {
|
||||
return new DataSourceInfo.Table(null, -1);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,8 +37,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
return 0;
|
||||
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,7 +76,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
int withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception;
|
||||
void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception;
|
||||
|
||||
/**
|
||||
* Reads multiple rows in bulk.
|
||||
|
@ -119,6 +118,17 @@ public interface TableReadConnection extends DataSourceReadConnection {
|
|||
var inputType = getDataType();
|
||||
var tCon = (TableWriteConnection) con;
|
||||
var mapping = tCon.createMapping(inputType);
|
||||
return withRows(tCon.writeLinesAcceptor(mapping.orElseThrow()));
|
||||
var acceptor = tCon.writeLinesAcceptor(mapping.orElseThrow());
|
||||
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
withRows(acc -> {
|
||||
if (!acceptor.accept(acc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
counter.getAndIncrement();
|
||||
return true;
|
||||
});
|
||||
return counter.get();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName;
|
|||
import io.xpipe.core.charsetter.NewLine;
|
||||
import io.xpipe.core.util.JacksonizedValue;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -69,6 +70,8 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
|
|||
|
||||
private final List<SecretValue> input;
|
||||
private final Integer timeout;
|
||||
|
||||
@Getter
|
||||
private final List<String> command;
|
||||
private final Charset charset;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
@ -125,4 +126,6 @@ public abstract class ProcessControl {
|
|||
public abstract InputStream getStderr();
|
||||
|
||||
public abstract Charset getCharset();
|
||||
|
||||
public abstract List<String> getCommand();
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
return getId() + "." + key;
|
||||
}
|
||||
|
||||
default Region configGui(Property<T> source, boolean all) {
|
||||
default Region configGui(Property<T> source, boolean preferQuiet) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
|
|||
}
|
||||
|
||||
default String getModuleName() {
|
||||
var n = getClass().getPackageName();
|
||||
var n = getClass().getModule().getName();
|
||||
var i = n.lastIndexOf('.');
|
||||
return i != -1 ? n.substring(i + 1) : n;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ public class CharChoiceComp extends Comp<CompStructure<HBox>> {
|
|||
if (customName != null) {
|
||||
rangeCopy.put(null, customName);
|
||||
}
|
||||
var choice = new ChoiceComp<Character>(value, rangeCopy);
|
||||
var choice = new ChoiceComp<Character>(value, rangeCopy, false);
|
||||
var charChoiceR = charChoice.createRegion();
|
||||
var choiceR = choice.createRegion();
|
||||
var box = new HBox(charChoiceR, choiceR);
|
||||
|
|
|
@ -22,15 +22,18 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
|||
|
||||
Property<T> value;
|
||||
ObservableValue<Map<T, ObservableValue<String>>> range;
|
||||
boolean includeNone;
|
||||
|
||||
public ChoiceComp(Property<T> value, Map<T, ObservableValue<String>> range) {
|
||||
public ChoiceComp(Property<T> value, Map<T, ObservableValue<String>> range, boolean includeNone) {
|
||||
this.value = value;
|
||||
this.range = new SimpleObjectProperty<>(range);
|
||||
this.includeNone = includeNone;
|
||||
}
|
||||
|
||||
public ChoiceComp(Property<T> value, ObservableValue<Map<T, ObservableValue<String>>> range) {
|
||||
public ChoiceComp(Property<T> value, ObservableValue<Map<T, ObservableValue<String>>> range, boolean includeNone) {
|
||||
this.value = value;
|
||||
this.range = PlatformThread.sync(range);
|
||||
this.includeNone = includeNone;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,7 +61,7 @@ public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
|
|||
});
|
||||
SimpleChangeListener.apply(range, c -> {
|
||||
var list = FXCollections.observableArrayList(c.keySet());
|
||||
if (!list.contains(null)) {
|
||||
if (!list.contains(null) && includeNone) {
|
||||
list.add(null);
|
||||
}
|
||||
cb.setItems(list);
|
||||
|
|
|
@ -31,7 +31,7 @@ public class DynamicOptionsComp extends Comp<CompStructure<FlowPane>> {
|
|||
public CompStructure<FlowPane> createBase() {
|
||||
var flow = new FlowPane(Orientation.HORIZONTAL);
|
||||
flow.setAlignment(Pos.CENTER);
|
||||
flow.setHgap(7);
|
||||
flow.setHgap(14);
|
||||
flow.setVgap(7);
|
||||
|
||||
var nameRegions = new ArrayList<Region>();
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.ValueNode;
|
||||
|
||||
import java.util.Currency;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DataTypeParser {
|
||||
|
||||
public static Optional<ValueNode> parseMonetary(String val) {
|
||||
for (Currency availableCurrency : Currency.getAvailableCurrencies()) {
|
||||
if (val.contains(availableCurrency.getSymbol())) {
|
||||
String newStr = DataTypeParserInternal.cleanseNumberString(val);
|
||||
var node = DataTypeParserInternal.parseDecimalFromCleansed(newStr);
|
||||
if (node.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Optional.of(ValueNode.ofCurrency(
|
||||
val, node.get().getMetaAttribute(DataStructureNode.DECIMAL_VALUE), availableCurrency));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Optional<ValueNode> parseNumber(String val) {
|
||||
var cleansed = DataTypeParserInternal.cleanseNumberString(val);
|
||||
return DataTypeParserInternal.parseNumberFromCleansed(cleansed);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.data.node.ValueNode;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DataTypeParserInternal {
|
||||
|
||||
private static final Pattern DECIMAL_IS_INTEGER = Pattern.compile("^([-\\d]+?)(\\.0*)?$");
|
||||
private static final Pattern TRAILING_ZEROS = Pattern.compile("^(.+?\\.\\d*)0+$");
|
||||
private static final Pattern LONG = Pattern.compile("^[+-]?[0-9]+$");
|
||||
private static final Pattern DECIMAL = Pattern.compile("^[+-]?([0-9]+)(\\.([0-9]+))?$");
|
||||
|
||||
static String cleanseNumberString(String value) {
|
||||
value = value.replaceAll("[^-\\d.]+", "");
|
||||
return value;
|
||||
}
|
||||
|
||||
static Optional<ValueNode> parseDecimalFromCleansed(String val) {
|
||||
// Normal decimal
|
||||
var simpleDecimal = parseSimpleDecimalValue(val);
|
||||
if (simpleDecimal.isPresent()) {
|
||||
return Optional.of(ValueNode.ofDecimal(val, simpleDecimal.get()));
|
||||
}
|
||||
|
||||
// Specially formatted number, must be in range of double
|
||||
if (NumberUtils.isCreatable(val)) {
|
||||
var number = NumberUtils.createNumber(val);
|
||||
if (number instanceof Float || number instanceof Double) {
|
||||
return Optional.of(ValueNode.ofDecimal(val, number.doubleValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Big decimal value
|
||||
try {
|
||||
var bigDecimal = new BigDecimal(val);
|
||||
return Optional.of(ValueNode.ofDecimal(bigDecimal));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> parseSimpleDecimalValue(String val) {
|
||||
var decimalMatcher = DECIMAL.matcher(val);
|
||||
if (decimalMatcher.matches()) {
|
||||
var integerMatcher = DECIMAL_IS_INTEGER.matcher(val);
|
||||
if (integerMatcher.matches()) {
|
||||
return Optional.of(integerMatcher.group(1));
|
||||
}
|
||||
|
||||
var trailingRemoved = TRAILING_ZEROS.matcher(val);
|
||||
if (trailingRemoved.matches()) {
|
||||
return Optional.of(trailingRemoved.group(1));
|
||||
}
|
||||
|
||||
return Optional.of(val);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
static Optional<ValueNode> parseNumberFromCleansed(String val) {
|
||||
// Simple integer
|
||||
if (LONG.matcher(val).matches()) {
|
||||
return Optional.of(ValueNode.ofInteger(val, val));
|
||||
}
|
||||
|
||||
// Simple decimal
|
||||
var simpleDecimal = parseSimpleDecimalValue(val);
|
||||
if (simpleDecimal.isPresent()) {
|
||||
return Optional.of(ValueNode.ofDecimal(val, simpleDecimal.get()));
|
||||
}
|
||||
|
||||
// Specially formatted number, must be in range of double or long
|
||||
if (NumberUtils.isCreatable(val)) {
|
||||
var number = NumberUtils.createNumber(val);
|
||||
if (number instanceof Float || number instanceof Double) {
|
||||
return Optional.of(ValueNode.ofDecimal(val, number.doubleValue()));
|
||||
} else {
|
||||
return Optional.of(ValueNode.ofInteger(val, number.longValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Big integer value
|
||||
try {
|
||||
var bigInteger = new BigInteger(val);
|
||||
return Optional.of(ValueNode.ofInteger(bigInteger));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
// Big decimal value
|
||||
try {
|
||||
var bigDecimal = new BigDecimal(val);
|
||||
return Optional.of(ValueNode.ofDecimal(bigDecimal));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ public class DialogHelper {
|
|||
}
|
||||
|
||||
public static Dialog dataStoreFlowQuery(DataFlow flow, DataFlow[] available) {
|
||||
return Dialog.choice("Flow", (DataFlow o) -> o.getDisplayName(), true, flow, available);
|
||||
return Dialog.choice("Flow", (DataFlow o) -> o.getDisplayName(), true, false, flow, available);
|
||||
}
|
||||
|
||||
public static Dialog shellQuery(String displayName, DataStore store) {
|
||||
|
@ -66,16 +66,20 @@ public class DialogHelper {
|
|||
});
|
||||
}
|
||||
|
||||
public static Dialog charsetQuery(StreamCharset c, boolean all) {
|
||||
return Dialog.query("Charset", false, true, c != null && !all, c, QueryConverter.CHARSET);
|
||||
public static Dialog charsetQuery(StreamCharset c, boolean preferQuiet) {
|
||||
return Dialog.query("Charset", false, true, c != null && preferQuiet, c, QueryConverter.CHARSET);
|
||||
}
|
||||
|
||||
public static Dialog newLineQuery(NewLine n, boolean all) {
|
||||
return Dialog.query("Newline", false, true, n != null && !all, n, QueryConverter.NEW_LINE);
|
||||
public static Dialog newLineQuery(NewLine n, boolean preferQuiet) {
|
||||
return Dialog.query("Newline", false, true, n != null && preferQuiet, n, QueryConverter.NEW_LINE);
|
||||
}
|
||||
|
||||
public static <T> Dialog query(String desc, T value, boolean required, QueryConverter<T> c, boolean all) {
|
||||
return Dialog.query(desc, false, required, value != null && !all, value, c);
|
||||
public static <T> Dialog query(String desc, T value, boolean required, QueryConverter<T> c, boolean preferQuiet) {
|
||||
return Dialog.query(desc, false, required, value != null && preferQuiet, value, c);
|
||||
}
|
||||
|
||||
public static Dialog booleanChoice(String desc, boolean value, boolean preferQuiet) {
|
||||
return Dialog.choice(desc, val -> val.toString(), true, preferQuiet, value, Boolean.TRUE, Boolean.FALSE);
|
||||
}
|
||||
|
||||
public static Dialog fileQuery(String name) {
|
||||
|
|
|
@ -69,7 +69,7 @@ public class DynamicOptionsBuilder {
|
|||
for (var e : NewLine.values()) {
|
||||
map.put(e, I18n.observable("extension." + e.getId()));
|
||||
}
|
||||
var comp = new ChoiceComp<>(prop, map);
|
||||
var comp = new ChoiceComp<>(prop, map, false);
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
|
@ -94,6 +94,14 @@ public class DynamicOptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public DynamicOptionsBuilder addToggle(String nameKey,
|
||||
Property<Boolean> prop) {
|
||||
var comp = new ToggleGroupComp<>(prop, new SimpleObjectProperty<>(Map.of(Boolean.TRUE, I18n.observable("extension.yes"), Boolean.FALSE, I18n.observable("extension.no"))));
|
||||
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <V> DynamicOptionsBuilder addToggle(
|
||||
Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
|
||||
var comp = new ToggleGroupComp<>(prop, new SimpleObjectProperty<>(names));
|
||||
|
@ -103,16 +111,18 @@ public class DynamicOptionsBuilder {
|
|||
}
|
||||
|
||||
public <V> DynamicOptionsBuilder addChoice(
|
||||
Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
|
||||
var comp = new ChoiceComp<>(prop, names);
|
||||
Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names, boolean includeNone
|
||||
) {
|
||||
var comp = new ChoiceComp<>(prop, names, includeNone);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <V> DynamicOptionsBuilder addChoice(
|
||||
Property<V> prop, ObservableValue<String> name, ObservableValue<Map<V, ObservableValue<String>>> names) {
|
||||
var comp = new ChoiceComp<>(prop, names);
|
||||
Property<V> prop, ObservableValue<String> name, ObservableValue<Map<V, ObservableValue<String>>> names, boolean includeNone
|
||||
) {
|
||||
var comp = new ChoiceComp<>(prop, names, includeNone);
|
||||
entries.add(new DynamicOptionsComp.Entry(name, comp));
|
||||
props.add(prop);
|
||||
return this;
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.core.store.DataStore;
|
|||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import io.xpipe.extension.XPipeServiceProviders;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
|
@ -13,12 +14,13 @@ import java.nio.file.Path;
|
|||
|
||||
public class ExtensionTest {
|
||||
|
||||
@SneakyThrows
|
||||
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);
|
||||
var file = Path.of(url.toURI()).toString();
|
||||
return FileStore.local(Path.of(file));
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ public class TypeConverter {
|
|||
if (NumberUtils.isCreatable(string)) {
|
||||
var number = NumberUtils.createNumber(string);
|
||||
if (number instanceof Float || number instanceof Double) {
|
||||
node.tag(DataStructureNode.IS_FLOATING_POINT);
|
||||
node.tag(DataStructureNode.IS_DECIMAL);
|
||||
} else {
|
||||
node.tag(DataStructureNode.IS_INTEGER);
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ public class TypeConverter {
|
|||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
if (node.hasMetaAttribute(DataStructureNode.FLOATING_POINT_VALUE)) {
|
||||
return new BigDecimal(node.getMetaAttribute(DataStructureNode.FLOATING_POINT_VALUE));
|
||||
if (node.hasMetaAttribute(DataStructureNode.DECIMAL_VALUE)) {
|
||||
return new BigDecimal(node.getMetaAttribute(DataStructureNode.DECIMAL_VALUE));
|
||||
}
|
||||
|
||||
var parsedDecimal = parseDecimal(node.asString());
|
||||
|
@ -101,8 +101,8 @@ public class TypeConverter {
|
|||
return parsedInteger;
|
||||
}
|
||||
|
||||
if (node.hasMetaAttribute(DataStructureNode.FLOATING_POINT_VALUE)) {
|
||||
return new BigDecimal(node.getMetaAttribute(DataStructureNode.FLOATING_POINT_VALUE)).toBigInteger();
|
||||
if (node.hasMetaAttribute(DataStructureNode.DECIMAL_VALUE)) {
|
||||
return new BigDecimal(node.getMetaAttribute(DataStructureNode.DECIMAL_VALUE)).toBigInteger();
|
||||
}
|
||||
|
||||
var parsedDecimal = parseDecimal(node.asString());
|
||||
|
|
|
@ -24,4 +24,6 @@ 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
|
||||
prependDescription=Prepends the new content to the existing content
|
||||
yes=Yes
|
||||
no=No
|
Loading…
Reference in a new issue