Add validators, rework machine stores

This commit is contained in:
Christopher Schnick committed 2022-07-03 20:41:42 +02:00
1 parent f0f1417980
commit 4eb0c80d60
23 files changed
+608 -33

No files matched your search

@@ -0,0 +1,103 @@
package io.xpipe.extension;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import net.synedra.validatorfx.Check;
import net.synedra.validatorfx.ValidationMessage;
import net.synedra.validatorfx.ValidationResult;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ChainedValidator implements io.xpipe.extension.Validator {
private final List<Validator> validators;
private final ReadOnlyObjectWrapper<ValidationResult> validationResultProperty = new ReadOnlyObjectWrapper<>(new ValidationResult());
private final ReadOnlyBooleanWrapper containsErrorsProperty = new ReadOnlyBooleanWrapper();
public ChainedValidator(List<Validator> validators) {
this.validators = validators;
validators.forEach(v -> {
v.containsErrorsProperty().addListener((c,o,n) -> {
containsErrorsProperty.set(containsErrors());
});
v.validationResultProperty().addListener((c,o,n) -> {
validationResultProperty.set(getValidationResult());
});
});
}
@Override
public Check createCheck() {
throw new UnsupportedOperationException();
}
@Override
public void add(Check check) {
throw new UnsupportedOperationException();
}
@Override
public void remove(Check check) {
throw new UnsupportedOperationException();
}
@Override
public ValidationResult getValidationResult() {
var list = new ArrayList<ValidationMessage>();
for (var val : validators) {
list.addAll(val.getValidationResult().getMessages());
}
var r = new ValidationResult();
r.addAll(list);
return r;
}
@Override
public ReadOnlyObjectProperty<ValidationResult> validationResultProperty() {
return validationResultProperty;
}
@Override
public ReadOnlyBooleanProperty containsErrorsProperty() {
return containsErrorsProperty;
}
@Override
public boolean containsErrors() {
return validators.stream().anyMatch(Validator::containsErrors);
}
@Override
public boolean validate() {
for (var val : validators) {
if (!val.validate()) {
return false;
}
}
return true;
}
@Override
public StringBinding createStringBinding() {
return createStringBinding("- ", "\n");
}
@Override
public StringBinding createStringBinding(String prefix, String separator) {
var list = new ArrayList<Observable>(validators.stream().map(Validator::createStringBinding).toList());
Observable[] observables = list.toArray(Observable[]::new);
return Bindings.createStringBinding(() -> {
return validators.stream().map(v -> v.createStringBinding(prefix, separator).get()).collect(Collectors.joining("\n"));
}, observables);
}
}
@@ -2,9 +2,10 @@ 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 javafx.beans.property.Property;
import javafx.scene.layout.Region;
import java.net.URI;
import java.util.List;
@@ -13,19 +14,25 @@ public interface DataStoreProvider {
enum Category {
STREAM,
MACHINE,
DATABASE;
}
default Category getCategory() {
if (StreamDataStore.class.isAssignableFrom(getStoreClasses().get(0))) {
var c = getStoreClasses().get(0);
if (StreamDataStore.class.isAssignableFrom(c)) {
return Category.STREAM;
}
if (MachineFileStore.class.isAssignableFrom(c) || ShellStore.class.isAssignableFrom(c)) {
return Category.MACHINE;
}
throw new ExtensionException("Provider " + getId() + " has no set category");
}
default Region createConfigGui(Property<DataStore> store) {
return null;
default GuiDialog guiDialog(Property<DataStore> store) {
throw new ExtensionException("Gui Dialog is not implemented by provider " + getId());
}
default void init() throws Exception {
@@ -65,7 +72,9 @@ public interface DataStoreProvider {
return null;
}
Dialog defaultDialog();
default Dialog defaultDialog() {
throw new ExtensionException("CLI Dialog not implemented by provider");
}
default String display(DataStore store) {
return store.toDisplay();
@@ -0,0 +1,83 @@
package io.xpipe.extension;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ObservableValue;
import net.synedra.validatorfx.Check;
import net.synedra.validatorfx.ValidationResult;
import java.util.ArrayList;
import java.util.Map;
public final class ExclusiveValidator<T> implements io.xpipe.extension.Validator {
private final Map<T, ? extends Validator> validators;
private final ObservableValue<T> obs;
public ExclusiveValidator(Map<T, ? extends Validator> validators, ObservableValue<T> obs) {
this.validators = validators;
this.obs = obs;
}
private Validator get() {
return validators.get(obs.getValue());
}
@Override
public Check createCheck() {
throw new UnsupportedOperationException();
}
@Override
public void add(Check check) {
throw new UnsupportedOperationException();
}
@Override
public void remove(Check check) {
throw new UnsupportedOperationException();
}
@Override
public ValidationResult getValidationResult() {
return get().getValidationResult();
}
@Override
public ReadOnlyObjectProperty<ValidationResult> validationResultProperty() {
return get().validationResultProperty();
}
@Override
public ReadOnlyBooleanProperty containsErrorsProperty() {
return get().containsErrorsProperty();
}
@Override
public boolean containsErrors() {
return get().containsErrors();
}
@Override
public boolean validate() {
return get().validate();
}
@Override
public StringBinding createStringBinding() {
return createStringBinding("- ", "\n");
}
@Override
public StringBinding createStringBinding(String prefix, String separator) {
var list = new ArrayList<Observable>(validators.values().stream().map(Validator::createStringBinding).toList());
list.add(obs);
Observable[] observables = list.toArray(Observable[]::new);
return Bindings.createStringBinding(() -> {
return get().createStringBinding(prefix, separator).get();
}, observables);
}
}
@@ -0,0 +1,18 @@
package io.xpipe.extension;
import io.xpipe.fxcomps.Comp;
import lombok.AllArgsConstructor;
import lombok.Value;
@Value
@AllArgsConstructor
public class GuiDialog {
Comp<?> comp;
Validator validator;
public GuiDialog(Comp<?> comp) {
this.comp = comp;
this.validator = new SimpleValidator();
}
}
@@ -0,0 +1,17 @@
package io.xpipe.extension;
import javafx.beans.property.Property;
import java.util.Map;
public class Properties {
public static <T, V> void bindExclusive(Property<V> selected, Map<V, ? extends Property<T>> map, Property<T> toBind) {
selected.addListener((c,o,n) -> {
toBind.unbind();
toBind.bind(map.get(n));
});
toBind.bind(map.get(selected.getValue()));
}
}
@@ -0,0 +1,118 @@
package io.xpipe.extension;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import net.synedra.validatorfx.Check;
import net.synedra.validatorfx.Severity;
import net.synedra.validatorfx.ValidationMessage;
import net.synedra.validatorfx.ValidationResult;
import java.util.*;
public class SimpleValidator implements Validator {
private final Map<Check, ChangeListener<ValidationResult>> checks = new LinkedHashMap<>();
private final ReadOnlyObjectWrapper<ValidationResult> validationResultProperty = new ReadOnlyObjectWrapper<>(new ValidationResult());
private final ReadOnlyBooleanWrapper containsErrorsProperty = new ReadOnlyBooleanWrapper();
/** Create a check that lives within this checker's domain.
* @return A check object whose dependsOn, decorates, etc. methods can be called
*/
public Check createCheck() {
Check check = new Check();
add(check);
return check;
}
/** Add another check to the checker. Changes in the check's validationResultProperty will be reflected in the checker.
* @param check The check to add.
*/
public void add(Check check) {
ChangeListener<ValidationResult> listener = (obs, oldv, newv) -> refreshProperties();
checks.put(check, listener);
check.validationResultProperty().addListener(listener);
}
/** Removes a check from this validator.
* @param check The check to remove from this validator.
*/
public void remove(Check check) {
ChangeListener<ValidationResult> listener = checks.remove(check);
if (listener != null) {
check.validationResultProperty().removeListener(listener);
}
refreshProperties();
}
/** Retrieves current validation result
* @return validation result
*/
public ValidationResult getValidationResult() {
return validationResultProperty.get();
}
/** Can be used to track validation result changes
* @return The Validation result property.
*/
public ReadOnlyObjectProperty<ValidationResult> validationResultProperty() {
return validationResultProperty.getReadOnlyProperty();
}
/** A read-only boolean property indicating whether any of the checks of this validator emitted an error. */
public ReadOnlyBooleanProperty containsErrorsProperty() {
return containsErrorsProperty.getReadOnlyProperty();
}
public boolean containsErrors() {
return containsErrorsProperty().get();
}
/** Run all checks (decorating nodes if appropriate)
* @return true if no errors were found, false otherwise
*/
public boolean validate() {
for (Check check : checks.keySet()) {
check.recheck();
}
return ! containsErrors();
}
private void refreshProperties() {
ValidationResult nextResult = new ValidationResult();
for (Check check : checks.keySet()) {
nextResult.addAll(check.getValidationResult().getMessages());
}
validationResultProperty.set(nextResult);
boolean hasErrors = false;
for (ValidationMessage msg : nextResult.getMessages()) {
hasErrors = hasErrors || msg.getSeverity() == Severity.ERROR;
}
containsErrorsProperty.set(hasErrors);
}
/** Create a string property that depends on the validation result.
* Each error message will be displayed on a separate line prefixed with a bullet.
*/
public StringBinding createStringBinding() {
return createStringBinding("- ", "\n");
}
@Override
public StringBinding createStringBinding(String prefix, String separator) {
return Bindings.createStringBinding( () -> {
StringBuilder str = new StringBuilder();
for (ValidationMessage msg : validationResultProperty.get().getMessages()) {
if (str.length() > 0) {
str.append(separator);
}
str.append(prefix).append(msg.getText());
}
return str.toString();
}, validationResultProperty);
}
}
@@ -0,0 +1,56 @@
package io.xpipe.extension;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import net.synedra.validatorfx.Check;
import net.synedra.validatorfx.ValidationResult;
public interface Validator {
public Check createCheck();
/** Add another check to the checker. Changes in the check's validationResultProperty will be reflected in the checker.
* @param check The check to add.
*/
public void add(Check check);
/** Removes a check from this validator.
* @param check The check to remove from this validator.
*/
public void remove(Check check);
/** Retrieves current validation result
* @return validation result
*/
public ValidationResult getValidationResult();
/** Can be used to track validation result changes
* @return The Validation result property.
*/
public ReadOnlyObjectProperty<ValidationResult> validationResultProperty();
/** A read-only boolean property indicating whether any of the checks of this validator emitted an error. */
public ReadOnlyBooleanProperty containsErrorsProperty();
public boolean containsErrors();
/** Run all checks (decorating nodes if appropriate)
* @return true if no errors were found, false otherwise
*/
public boolean validate();
/** Create a string property that depends on the validation result.
* Each error message will be displayed on a separate line prefixed with a bullet.
* @return
*/
public StringBinding createStringBinding();
/** Create a string property that depends on the validation result.
* @param prefix The string to prefix each validation message with
* @param separator The string to separate consecutive validation messages with
* @param severities The severities to consider; If none is given, only Severity.ERROR will be considered
* @return
*/
public StringBinding createStringBinding(String prefix, String separator);
}
@@ -0,0 +1,15 @@
package io.xpipe.extension;
import javafx.beans.value.ObservableValue;
import net.synedra.validatorfx.Check;
public class Validators {
public static Check nonNull(Validator v, ObservableValue<String> name, ObservableValue<?> s) {
return v.createCheck().dependsOn("val", s).withMethod(c -> {
if (c.get("val") == null ) {
c.error(I18n.get("extension.mustNotBeEmpty", name.getValue()));
}
});
}
}
@@ -3,11 +3,14 @@ package io.xpipe.extension.comp;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.Secret;
import io.xpipe.extension.I18n;
import io.xpipe.extension.Validator;
import io.xpipe.extension.Validators;
import io.xpipe.fxcomps.Comp;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import net.synedra.validatorfx.Check;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import java.nio.charset.Charset;
@@ -15,7 +18,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
public class DynamicOptionsBuilder<T> {
@@ -40,6 +43,17 @@ public class DynamicOptionsBuilder<T> {
this.title = title;
}
public DynamicOptionsBuilder<T> decorate(Check c) {
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
return this;
}
public DynamicOptionsBuilder<T> nonNull(Validator v) {
var e = entries.get(entries.size() - 1);
var p = props.get(props.size() - 1);
return decorate(Validators.nonNull(v, e.name(), p));
}
public DynamicOptionsBuilder<T> addNewLine(Property<NewLine> prop) {
var map = new LinkedHashMap<NewLine, ObservableValue<String>>();
for (var e : NewLine.values()) {
@@ -79,6 +93,13 @@ public class DynamicOptionsBuilder<T> {
return this;
}
public DynamicOptionsBuilder<T> addString(String nameKey, Property<String> prop) {
var comp = new TextFieldComp(prop);
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
props.add(prop);
return this;
}
public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) {
var comp = new TextFieldComp(prop);
entries.add(new DynamicOptionsComp.Entry(name, comp));
@@ -86,8 +107,8 @@ public class DynamicOptionsBuilder<T> {
return this;
}
public DynamicOptionsBuilder<T> addComp(Comp<?> comp) {
entries.add(new DynamicOptionsComp.Entry(null, comp));
public DynamicOptionsBuilder<T> addComp(ObservableValue<String> name, Comp<?> comp) {
entries.add(new DynamicOptionsComp.Entry(name, comp));
return this;
}
@@ -105,19 +126,20 @@ public class DynamicOptionsBuilder<T> {
return this;
}
public <V extends T> Comp<?> buildComp(Function<T, V> creator, Property<T> toSet) {
public <V extends T> Comp<?> buildComp(Supplier<V> creator, Property<T> toSet) {
props.forEach(prop -> {
prop.addListener((c,o,n) -> {
toSet.setValue(creator.apply(toSet.getValue()));
toSet.setValue(creator.get());
});
});
toSet.setValue(creator.get());
if (title != null) {
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
}
return new DynamicOptionsComp(entries, wrap);
}
public <V extends T> Region build(Function<T, V> creator, Property<T> toSet) {
public <V extends T> Region build(Supplier<V> creator, Property<T> toSet) {
return buildComp(creator, toSet).createRegion();
}
}
@@ -4,14 +4,18 @@ import com.jfoenix.controls.JFXTabPane;
import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure;
import io.xpipe.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import lombok.Getter;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@Getter
public class TabPaneComp extends Comp<CompStructure<JFXTabPane>> {
@Override
@@ -32,12 +36,24 @@ public class TabPaneComp extends Comp<CompStructure<JFXTabPane>> {
content.prefWidthProperty().bind(tabPane.widthProperty());
}
tabPane.getSelectionModel().select(entries.indexOf(selected.getValue()));
tabPane.getSelectionModel().selectedIndexProperty().addListener((c,o,n) -> {
selected.setValue(entries.get(n.intValue()));
});
selected.addListener((c,o,n) -> {
PlatformThread.runLaterIfNeeded(() -> {
tabPane.getSelectionModel().select(entries.indexOf(n));
});
});
return new SimpleCompStructure<>(tabPane);
}
private final Property<Entry> selected;
private final List<Entry> entries;
public TabPaneComp(List<Entry> entries) {
public TabPaneComp(Property<Entry> selected, List<Entry> entries) {
this.selected = selected;
this.entries = entries;
}
+1
View File
@@ -21,6 +21,7 @@ module io.xpipe.extension {
requires static org.controlsfx.controls;
requires java.desktop;
requires org.fxmisc.richtext;
requires static net.synedra.validatorfx;
requires org.fxmisc.flowless;
requires org.fxmisc.undofx;
requires org.fxmisc.wellbehavedfx;