diff --git a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java index ad088c7a3..6538f947d 100644 --- a/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java +++ b/api/src/main/java/io/xpipe/api/impl/DataTableImpl.java @@ -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); diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/api/QueryTableDataExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/api/QueryTableDataExchange.java index 9ba81da50..7cd1a6302 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/api/QueryTableDataExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/api/QueryTableDataExchange.java @@ -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; + } } diff --git a/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java b/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java index 70985121e..b9b5ca653 100644 --- a/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/DataStructureNode.java @@ -17,12 +17,18 @@ public abstract class DataStructureNode implements Iterable { 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 metaAttributes; public void clearMetaAttributes() { @@ -58,12 +64,12 @@ public abstract class DataStructureNode implements Iterable { 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 { return "(" + (metaAttributes != null ? metaAttributes.entrySet().stream() + .sorted(Comparator.comparingInt(entry -> entry.getKey())) .map(e -> e.getValue() != null ? e.getKey() + ":" + e.getValue() : e.getKey().toString()) diff --git a/core/src/main/java/io/xpipe/core/data/node/ValueNode.java b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java index 4e1090bdc..3957303d8 100644 --- a/core/src/main/java/io/xpipe/core/data/node/ValueNode.java +++ b/core/src/main/java/io/xpipe/core/data/node/ValueNode.java @@ -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; } diff --git a/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java b/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java index 9ffd423af..cf93db3cb 100644 --- a/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java @@ -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 elements; private final boolean required; + private final boolean quiet; private int selected; @JsonCreator - public ChoiceElement(String description, List elements, boolean required, int selected) { + public ChoiceElement(String description, List 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 diff --git a/core/src/main/java/io/xpipe/core/dialog/Dialog.java b/core/src/main/java/io/xpipe/core/dialog/Dialog.java index 10b9fadf4..f0908c70c 100644 --- a/core/src/main/java/io/xpipe/core/dialog/Dialog.java +++ b/core/src/main/java/io/xpipe/core/dialog/Dialog.java @@ -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> completion = new ArrayList<>(); + private final List> 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 elements, boolean required, int selected) { - Dialog.Choice c = new Dialog.Choice(description, elements, required, selected); + String description, List 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 Dialog.Choice choice( - String description, Function toString, boolean required, T def, T... vals) { + String description, Function 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 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 s) { completion.add(s); return this; } @@ -419,7 +423,7 @@ public abstract class Dialog { return this; } - public Dialog onCompletion(List> s) { + public Dialog onCompletion(List> s) { completion.addAll(s); return this; } @@ -430,13 +434,13 @@ public abstract class Dialog { } @SuppressWarnings("unchecked") - public void complete() { + public void complete() throws Exception { if (evaluation != null) { eval = evaluation.get(); - completion.forEach(c -> { - Consumer ct = (Consumer) c; + for (Charsetter.FailableConsumer c : completion) { + Charsetter.FailableConsumer ct = (Charsetter.FailableConsumer) c; ct.accept((T) eval); - }); + } } } @@ -459,8 +463,8 @@ public abstract class Dialog { private final ChoiceElement element; - private Choice(String description, List elements, boolean required, int selected) { - this.element = new ChoiceElement(description, elements, required, selected); + private Choice(String description, List elements, boolean required, boolean quiet, int selected) { + this.element = new ChoiceElement(description, elements, required, quiet, selected); } @Override diff --git a/core/src/main/java/io/xpipe/core/impl/BufferedTableReadConnection.java b/core/src/main/java/io/xpipe/core/impl/BufferedTableReadConnection.java index 26ebd1942..1bdead804 100644 --- a/core/src/main/java/io/xpipe/core/impl/BufferedTableReadConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/BufferedTableReadConnection.java @@ -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 lineAcceptor) throws Exception { - AtomicInteger localCounter = new AtomicInteger(); + public void withRows(DataStructureNodeAcceptor lineAcceptor) throws Exception { TupleNode node; while (((node = get()) != null)) { var returned = lineAcceptor.accept(node); if (!returned) { break; } - - localCounter.getAndIncrement(); } - return localCounter.get(); } @Override diff --git a/core/src/main/java/io/xpipe/core/impl/LimitTableReadConnection.java b/core/src/main/java/io/xpipe/core/impl/LimitTableReadConnection.java index f01141739..ca38432eb 100644 --- a/core/src/main/java/io/xpipe/core/impl/LimitTableReadConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/LimitTableReadConnection.java @@ -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 lineAcceptor) throws Exception { - AtomicInteger localCounter = new AtomicInteger(); + public void withRows(DataStructureNodeAcceptor 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 diff --git a/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java b/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java index 7ff8651b6..8e491292f 100644 --- a/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/XpbtReadConnection.java @@ -60,15 +60,14 @@ public class XpbtReadConnection extends StreamReadConnection implements TableRea } @Override - public int withRows(DataStructureNodeAcceptor lineAcceptor) throws Exception { + public void withRows(DataStructureNodeAcceptor lineAcceptor) throws Exception { if (empty) { - return 0; + return; } var reader = TypedDataStructureNodeReader.of(dataType); AtomicBoolean quit = new AtomicBoolean(false); AtomicReference 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 diff --git a/core/src/main/java/io/xpipe/core/source/DataSource.java b/core/src/main/java/io/xpipe/core/source/DataSource.java index d4c2eb4e2..ddf849f81 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSource.java +++ b/core/src/main/java/io/xpipe/core/source/DataSource.java @@ -58,6 +58,10 @@ public abstract class DataSource extends JacksonizedValue store.checkComplete(); } + public void validate() throws Exception { + store.validate(); + } + public List getAvailableWriteModes() { if (getFlow() != null && !getFlow().hasOutput()) { return List.of(); diff --git a/core/src/main/java/io/xpipe/core/source/TableDataSource.java b/core/src/main/java/io/xpipe/core/source/TableDataSource.java index 3829474a5..d60e8c873 100644 --- a/core/src/main/java/io/xpipe/core/source/TableDataSource.java +++ b/core/src/main/java/io/xpipe/core/source/TableDataSource.java @@ -25,7 +25,13 @@ public abstract class TableDataSource extends DataSource lineAcceptor) throws Exception { - return 0; + public void withRows(DataStructureNodeAcceptor lineAcceptor) throws Exception { } @Override @@ -77,7 +76,7 @@ public interface TableReadConnection extends DataSourceReadConnection { * * @return */ - int withRows(DataStructureNodeAcceptor lineAcceptor) throws Exception; + void withRows(DataStructureNodeAcceptor 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(); } } diff --git a/core/src/main/java/io/xpipe/core/store/LocalStore.java b/core/src/main/java/io/xpipe/core/store/LocalStore.java index fb8b484c1..5f3f9270b 100644 --- a/core/src/main/java/io/xpipe/core/store/LocalStore.java +++ b/core/src/main/java/io/xpipe/core/store/LocalStore.java @@ -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 input; private final Integer timeout; + + @Getter private final List command; private final Charset charset; diff --git a/core/src/main/java/io/xpipe/core/store/ProcessControl.java b/core/src/main/java/io/xpipe/core/store/ProcessControl.java index e00fdd7dc..2caeaa3e0 100644 --- a/core/src/main/java/io/xpipe/core/store/ProcessControl.java +++ b/core/src/main/java/io/xpipe/core/store/ProcessControl.java @@ -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 getCommand(); } diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java index c50816452..31b29dabd 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java @@ -43,7 +43,7 @@ public interface DataSourceProvider> { return getId() + "." + key; } - default Region configGui(Property source, boolean all) { + default Region configGui(Property source, boolean preferQuiet) throws Exception { return null; } @@ -56,7 +56,7 @@ public interface DataSourceProvider> { } default String getModuleName() { - var n = getClass().getPackageName(); + var n = getClass().getModule().getName(); var i = n.lastIndexOf('.'); return i != -1 ? n.substring(i + 1) : n; } diff --git a/extension/src/main/java/io/xpipe/extension/comp/CharChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/CharChoiceComp.java index 53c8a3aac..1a626af77 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/CharChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/CharChoiceComp.java @@ -33,7 +33,7 @@ public class CharChoiceComp extends Comp> { if (customName != null) { rangeCopy.put(null, customName); } - var choice = new ChoiceComp(value, rangeCopy); + var choice = new ChoiceComp(value, rangeCopy, false); var charChoiceR = charChoice.createRegion(); var choiceR = choice.createRegion(); var box = new HBox(charChoiceR, choiceR); diff --git a/extension/src/main/java/io/xpipe/extension/comp/ChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/ChoiceComp.java index cd05a7801..629a4168f 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/ChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/ChoiceComp.java @@ -22,15 +22,18 @@ public class ChoiceComp extends Comp>> { Property value; ObservableValue>> range; + boolean includeNone; - public ChoiceComp(Property value, Map> range) { + public ChoiceComp(Property value, Map> range, boolean includeNone) { this.value = value; this.range = new SimpleObjectProperty<>(range); + this.includeNone = includeNone; } - public ChoiceComp(Property value, ObservableValue>> range) { + public ChoiceComp(Property value, ObservableValue>> range, boolean includeNone) { this.value = value; this.range = PlatformThread.sync(range); + this.includeNone = includeNone; } @Override @@ -58,7 +61,7 @@ public class ChoiceComp extends Comp>> { }); 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); diff --git a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java index 769964906..b3ceb7948 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java @@ -31,7 +31,7 @@ public class DynamicOptionsComp extends Comp> { public CompStructure createBase() { var flow = new FlowPane(Orientation.HORIZONTAL); flow.setAlignment(Pos.CENTER); - flow.setHgap(7); + flow.setHgap(14); flow.setVgap(7); var nameRegions = new ArrayList(); diff --git a/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java b/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java new file mode 100644 index 000000000..951e2db1c --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/DataTypeParser.java @@ -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 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 parseNumber(String val) { + var cleansed = DataTypeParserInternal.cleanseNumberString(val); + return DataTypeParserInternal.parseNumberFromCleansed(cleansed); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/util/DataTypeParserInternal.java b/extension/src/main/java/io/xpipe/extension/util/DataTypeParserInternal.java new file mode 100644 index 000000000..8bf3c4dc9 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/DataTypeParserInternal.java @@ -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 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 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 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(); + } +} diff --git a/extension/src/main/java/io/xpipe/extension/util/DialogHelper.java b/extension/src/main/java/io/xpipe/extension/util/DialogHelper.java index d13ff3eb8..e4499743f 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DialogHelper.java +++ b/extension/src/main/java/io/xpipe/extension/util/DialogHelper.java @@ -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 Dialog query(String desc, T value, boolean required, QueryConverter c, boolean all) { - return Dialog.query(desc, false, required, value != null && !all, value, c); + public static Dialog query(String desc, T value, boolean required, QueryConverter 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) { diff --git a/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java index 006f144e9..a8d31690f 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java +++ b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java @@ -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 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 DynamicOptionsBuilder addToggle( Property prop, ObservableValue name, Map> names) { var comp = new ToggleGroupComp<>(prop, new SimpleObjectProperty<>(names)); @@ -103,16 +111,18 @@ public class DynamicOptionsBuilder { } public DynamicOptionsBuilder addChoice( - Property prop, ObservableValue name, Map> names) { - var comp = new ChoiceComp<>(prop, names); + Property prop, ObservableValue name, Map> names, boolean includeNone + ) { + var comp = new ChoiceComp<>(prop, names, includeNone); entries.add(new DynamicOptionsComp.Entry(name, comp)); props.add(prop); return this; } public DynamicOptionsBuilder addChoice( - Property prop, ObservableValue name, ObservableValue>> names) { - var comp = new ChoiceComp<>(prop, names); + Property prop, ObservableValue name, ObservableValue>> names, boolean includeNone + ) { + var comp = new ChoiceComp<>(prop, names, includeNone); entries.add(new DynamicOptionsComp.Entry(name, comp)); props.add(prop); return this; diff --git a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java index 6934db3bd..fa793b47b 100644 --- a/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java +++ b/extension/src/main/java/io/xpipe/extension/util/ExtensionTest.java @@ -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)); } diff --git a/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java b/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java index 449db178f..4c4d59864 100644 --- a/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java +++ b/extension/src/main/java/io/xpipe/extension/util/TypeConverter.java @@ -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()); diff --git a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties index 359e76869..555993963 100644 --- a/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties +++ b/extension/src/main/resources/io/xpipe/extension/resources/lang/translations_en.properties @@ -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 \ No newline at end of file +prependDescription=Prepends the new content to the existing content +yes=Yes +no=No \ No newline at end of file