From bd82ae0af01689b1636cedf1ab5c86816ef7004e Mon Sep 17 00:00:00 2001 From: slskiba <161473135+slskiba@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:06:10 +0200 Subject: [PATCH] #438: add ValidationState (#569) --- .../tools/ide/commandlet/Commandlet.java | 17 ++--- .../ide/commandlet/InstallCommandlet.java | 8 ++- .../tools/ide/commandlet/ShellCommandlet.java | 2 +- .../tools/ide/context/AbstractIdeContext.java | 41 ++++++----- .../ide/property/CommandletProperty.java | 7 +- .../tools/ide/property/EditionProperty.java | 7 +- .../tools/ide/property/FileProperty.java | 7 +- .../tools/ide/property/FolderProperty.java | 7 +- .../tools/ide/property/LocaleProperty.java | 6 +- .../tools/ide/property/NumberProperty.java | 7 +- .../tools/ide/property/PathProperty.java | 29 ++++---- .../tools/ide/property/PluginProperty.java | 7 +- .../devonfw/tools/ide/property/Property.java | 28 +++++--- .../ide/property/RepositoryProperty.java | 6 +- .../tools/ide/property/StringProperty.java | 7 +- .../tools/ide/property/ToolProperty.java | 7 +- .../tools/ide/property/VersionProperty.java | 6 +- .../ide/validation/PropertyValidator.java | 8 +++ .../ide/validation/ValidationResult.java | 9 +++ .../tools/ide/validation/ValidationState.java | 50 +++++++++++++ .../ide/validation/ValidationStateTest.java | 70 +++++++++++++++++++ 21 files changed, 243 insertions(+), 93 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/validation/PropertyValidator.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/validation/ValidationResult.java create mode 100644 cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java create mode 100644 cli/src/test/java/com/devonfw/tools/ide/validation/ValidationStateTest.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java index bed28e78e..8e20efc8c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java @@ -13,6 +13,8 @@ import com.devonfw.tools.ide.property.Property; import com.devonfw.tools.ide.tool.ToolCommandlet; import com.devonfw.tools.ide.tool.plugin.PluginDescriptor; +import com.devonfw.tools.ide.validation.ValidationResult; +import com.devonfw.tools.ide.validation.ValidationState; import com.devonfw.tools.ide.version.VersionIdentifier; /** @@ -219,20 +221,13 @@ public boolean isProcessableOutput() { * @return {@code true} if this {@link Commandlet} is the valid candidate to be {@link #run()}, {@code false} otherwise. * @see Property#validate() */ - public boolean validate() { - + public ValidationResult validate() { + ValidationState state = new ValidationState(null); // avoid validation exception if not a candidate to be run. for (Property property : this.propertiesList) { - if (property.isRequired() && (property.getValue() == null)) { - return false; - } - } - for (Property property : this.propertiesList) { - if (!property.validate()) { - return false; - } + state.add(property.validate()); } - return true; + return state; } /** diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java index 79d4fad41..4ea01b581 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/InstallCommandlet.java @@ -4,6 +4,7 @@ import com.devonfw.tools.ide.property.ToolProperty; import com.devonfw.tools.ide.property.VersionProperty; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.validation.ValidationState; import com.devonfw.tools.ide.version.VersionIdentifier; /** @@ -29,7 +30,12 @@ public InstallCommandlet(IdeContext context) { super(context); addKeyword(getName()); this.tool = add(new ToolProperty("", true, "tool")); - this.version = add(new VersionProperty("", false, "version")); + this.version = add(new VersionProperty("", false, "version", + (v, state) -> { + if (!v.isValid()) { + state.addErrorMessage("Given version " + v + " is not a valid version"); + } + })); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java index a20a08743..b2d14a6da 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java @@ -199,6 +199,6 @@ private boolean apply(CliArgument argument, Commandlet commandlet) { } currentArgument = currentArgument.getNext(!endOpts); } - return commandlet.validate(); + return commandlet.validate().isValid(); } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 6118a3ad3..a50e467f7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -50,6 +50,8 @@ import com.devonfw.tools.ide.step.Step; import com.devonfw.tools.ide.step.StepImpl; import com.devonfw.tools.ide.url.model.UrlMetadata; +import com.devonfw.tools.ide.validation.ValidationResult; +import com.devonfw.tools.ide.validation.ValidationState; import com.devonfw.tools.ide.variable.IdeVariables; /** @@ -774,10 +776,10 @@ public int run(CliArguments arguments) { if (!current.isEnd()) { String keyword = current.get(); firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); - boolean matches; + ValidationResult firstResult = null; if (firstCandidate != null) { - matches = applyAndRun(arguments.copy(), firstCandidate); - if (matches) { + firstResult = applyAndRun(arguments.copy(), firstCandidate); + if (firstResult.isValid()) { supressStepSuccess = firstCandidate.isSuppressStepSuccess(); step.success(); return ProcessResult.SUCCESS; @@ -785,14 +787,17 @@ public int run(CliArguments arguments) { } for (Commandlet cmd : this.commandletManager.getCommandlets()) { if (cmd != firstCandidate) { - matches = applyAndRun(arguments.copy(), cmd); - if (matches) { + ValidationResult result = applyAndRun(arguments.copy(), cmd); + if (result.isValid()) { supressStepSuccess = cmd.isSuppressStepSuccess(); step.success(); return ProcessResult.SUCCESS; } } } + if (firstResult != null) { + throw new CliException(firstResult.getErrorMessage()); + } step.error("Invalid arguments: {}", current.getArgs()); } @@ -818,15 +823,15 @@ public int run(CliArguments arguments) { * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the * {@link Commandlet} did not match and we have to try a different candidate). */ - private boolean applyAndRun(CliArguments arguments, Commandlet cmd) { + private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) { cmd.clearProperties(); - boolean matches = apply(arguments, cmd, null); - if (matches) { - matches = cmd.validate(); + ValidationResult result = apply(arguments, cmd, null); + if (result.isValid()) { + result = cmd.validate(); } - if (matches) { + if (result.isValid()) { debug("Running commandlet {}", cmd); if (cmd.isIdeHomeRequired() && (this.ideHome == null)) { throw new CliException(getMessageIdeHomeNotFound(), ProcessResult.NO_IDE_HOME); @@ -868,7 +873,7 @@ private boolean applyAndRun(CliArguments arguments, Commandlet cmd) { } else { trace("Commandlet did not match"); } - return matches; + return result; } /** @@ -914,7 +919,7 @@ private boolean completeCommandlet(CliArguments arguments, Commandlet cmd, Compl if (cmd.isIdeHomeRequired() && (this.ideHome == null)) { return false; } else { - return apply(arguments.copy(), cmd, collector); + return apply(arguments.copy(), cmd, collector).isValid(); } } @@ -927,7 +932,7 @@ private boolean completeCommandlet(CliArguments arguments, Commandlet cmd, Compl * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have been applied (set in the {@link Commandlet} * and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate). */ - public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) { + public ValidationResult apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) { trace("Trying to match arguments to commandlet {}", cmd.getName()); CliArgument currentArgument = arguments.current(); @@ -952,7 +957,9 @@ public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidate } if (currentProperty == null) { trace("No option or next value found"); - return false; + ValidationState state = new ValidationState(null); + state.addErrorMessage("No matching property found"); + return state; } trace("Next property candidate to match argument is {}", currentProperty); if (currentProperty == property) { @@ -969,11 +976,13 @@ public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidate } boolean matches = currentProperty.apply(arguments, this, cmd, collector); if (!matches || currentArgument.isCompletion()) { - return false; + ValidationState state = new ValidationState(null); + state.addErrorMessage("No matching property found"); + return state; } currentArgument = arguments.current(); } - return true; + return new ValidationState(null); } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java index f6af09c6e..76b42d0aa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java @@ -1,10 +1,9 @@ package com.devonfw.tools.ide.property; -import java.util.function.Consumer; - import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link Property} with {@link #getValueType() value type} {@link Commandlet}. @@ -29,9 +28,9 @@ public CommandletProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public CommandletProperty(String name, boolean required, String alias, Consumer validator) { + public CommandletProperty(String name, boolean required, String alias, PropertyValidator validator) { super(name, required, alias, false, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java index 36d10dd41..7cc2d782b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/EditionProperty.java @@ -1,8 +1,7 @@ package com.devonfw.tools.ide.property; -import java.util.function.Consumer; - import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; public class EditionProperty extends Property { @@ -24,9 +23,9 @@ public EditionProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public EditionProperty(String name, boolean required, String alias, Consumer validator) { + public EditionProperty(String name, boolean required, String alias, PropertyValidator validator) { super(name, required, alias, false, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java index 15229c10a..2eea9d925 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/FileProperty.java @@ -1,7 +1,8 @@ package com.devonfw.tools.ide.property; import java.nio.file.Path; -import java.util.function.Consumer; + +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link PathProperty} for a file. @@ -28,9 +29,9 @@ public FileProperty(String name, boolean required, String alias, boolean mustExi * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public FileProperty(String name, boolean required, String alias, boolean mustExist, Consumer validator) { + public FileProperty(String name, boolean required, String alias, boolean mustExist, PropertyValidator validator) { super(name, required, alias, mustExist, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java index da30bea9e..ff776533f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/FolderProperty.java @@ -1,7 +1,8 @@ package com.devonfw.tools.ide.property; import java.nio.file.Path; -import java.util.function.Consumer; + +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link PathProperty} for a folder (directory). @@ -28,9 +29,9 @@ public FolderProperty(String name, boolean required, String alias, boolean mustE * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public FolderProperty(String name, boolean required, String alias, boolean mustExist, Consumer validator) { + public FolderProperty(String name, boolean required, String alias, boolean mustExist, PropertyValidator validator) { super(name, required, alias, mustExist, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java index 31fee10ce..ac90578dd 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/LocaleProperty.java @@ -2,11 +2,11 @@ import java.util.Arrays; import java.util.Locale; -import java.util.function.Consumer; import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link Property} with {@link Locale} as {@link #getValueType() value type}. @@ -33,9 +33,9 @@ public LocaleProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public LocaleProperty(String name, boolean required, String alias, Consumer validator) { + public LocaleProperty(String name, boolean required, String alias, PropertyValidator validator) { super(name, required, alias, false, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java index ca0bf8277..f13c59bb3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/NumberProperty.java @@ -1,8 +1,7 @@ package com.devonfw.tools.ide.property; -import java.util.function.Consumer; - import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link Property} with {@link #getValueType() value type} {@link Long}. @@ -27,9 +26,9 @@ public NumberProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public NumberProperty(String name, boolean required, String alias, Consumer validator) { + public NumberProperty(String name, boolean required, String alias, PropertyValidator validator) { super(name, required, alias, false, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java index 04c9a8344..f87c4e2d3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/PathProperty.java @@ -3,13 +3,14 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.function.Consumer; import java.util.stream.Stream; -import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; +import com.devonfw.tools.ide.validation.ValidationResult; +import com.devonfw.tools.ide.validation.ValidationState; /** * {@link Property} with {@link Path} as {@link #getValueType() value type}. @@ -38,9 +39,9 @@ public PathProperty(String name, boolean required, String alias, boolean mustExi * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public PathProperty(String name, boolean required, String alias, boolean mustExist, Consumer validator) { + public PathProperty(String name, boolean required, String alias, boolean mustExist, PropertyValidator validator) { super(name, required, alias, false, validator); this.mustExist = mustExist; @@ -59,26 +60,26 @@ public Path parse(String valueAsString, IdeContext context) { } @Override - public boolean validate() { - + public ValidationResult validate() { + ValidationState state = new ValidationState(this.getNameOrAlias()); for (Path path : this.value) { if (path != null && Files.exists(path)) { if (isPathRequiredToBeFile() && !Files.isRegularFile(path)) { - throw new CliException("Path " + path + " is not a file."); + state.addErrorMessage("Path " + path + " is not a file."); } else if (isPathRequiredToBeFolder() && !Files.isDirectory(path)) { - throw new CliException("Path " + path + " is not a folder."); + state.addErrorMessage("Path " + path + " is not a folder."); } } else if (isPathRequiredToExist()) { - throw new CliException("Path " + path + " does not exist."); + state.addErrorMessage("Path " + path + " does not exist."); } } - return super.validate(); - + state.add(super.validate()); + return state; } /** * @return {@code true} if the {@link Path} {@link #getValue() value} must {@link Files#exists(Path, java.nio.file.LinkOption...) exist} if set, {@code false} - * otherwise. + * otherwise. */ protected boolean isPathRequiredToExist() { @@ -87,7 +88,7 @@ protected boolean isPathRequiredToExist() { /** * @return {@code true} if the {@link Path} {@link #getValue() value} must be a {@link Files#isDirectory(Path, java.nio.file.LinkOption...) folder} if it - * exists, {@code false} otherwise. + * exists, {@code false} otherwise. */ protected boolean isPathRequiredToBeFolder() { @@ -96,7 +97,7 @@ protected boolean isPathRequiredToBeFolder() { /** * @return {@code true} if the {@link Path} {@link #getValue() value} must be a {@link Files#isRegularFile(Path, java.nio.file.LinkOption...) file} if it - * exists, {@code false} otherwise. + * exists, {@code false} otherwise. */ protected boolean isPathRequiredToBeFile() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java index 38d138a04..e022f5445 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/PluginProperty.java @@ -1,7 +1,5 @@ package com.devonfw.tools.ide.property; -import java.util.function.Consumer; - import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; @@ -9,6 +7,7 @@ import com.devonfw.tools.ide.tool.plugin.PluginBasedCommandlet; import com.devonfw.tools.ide.tool.plugin.PluginDescriptor; import com.devonfw.tools.ide.tool.plugin.PluginMaps; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link Property} representing the plugin of a {@link PluginBasedCommandlet}. @@ -34,9 +33,9 @@ public PluginProperty(String name, boolean required, String alias) { * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param multivalued the boolean flag about multiple arguments - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public PluginProperty(String name, boolean required, String alias, boolean multivalued, Consumer validator) { + public PluginProperty(String name, boolean required, String alias, boolean multivalued, PropertyValidator validator) { super(name, required, alias, multivalued, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index 193854a31..01740e322 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -11,6 +11,9 @@ import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.completion.CompletionCandidateCollectorAdapter; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; +import com.devonfw.tools.ide.validation.ValidationResult; +import com.devonfw.tools.ide.validation.ValidationState; /** * A {@link Property} is a simple container for a {@link #getValue() value} with a fixed {@link #getName() name} and {@link #getValueType() type}. Further we @@ -37,7 +40,7 @@ public abstract class Property { /** @see #isRequired() */ protected final boolean required; - private final Consumer validator; + private final PropertyValidator validator; /** @see #isMultiValued() */ private final boolean multivalued; @@ -71,7 +74,7 @@ public Property(String name, boolean required, String alias) { * @param multivalued the boolean flag about multiple arguments * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. */ - public Property(String name, boolean required, String alias, boolean multivalued, Consumer validator) { + public Property(String name, boolean required, String alias, boolean multivalued, PropertyValidator validator) { super(); this.name = name; @@ -111,7 +114,7 @@ public String getNameOrAlias() { /** * @return {@code true} if this property is required (if argument is not present the {@link Commandlet} cannot be invoked), {@code false} otherwise (if - * optional). + * optional). */ public boolean isRequired() { @@ -139,7 +142,7 @@ public boolean isOption() { /** * @return {@code true} if this {@link Property} forces an implicit {@link CliArgument#isEndOptions() end-options} as if "--" was provided before its first - * {@link CliArgument argument}. + * {@link CliArgument argument}. */ public boolean isEndOptions() { @@ -449,21 +452,24 @@ public boolean matches(String nameOrAlias) { /** * @return {@code true} if this {@link Property} is valid, {@code false} if it is {@link #isRequired() required} but no {@link #getValue() value} has been - * set. - * @throws RuntimeException if the {@link #getValue() value} is violating given constraints. This is checked by the optional {@link Consumer} function given - * at construction time. + * set. + * @throws RuntimeException if the {@link #getValue() value} is violating given constraints. This is checked by the optional {@link Consumer} function + * given at construction time. */ - public boolean validate() { + public ValidationResult validate() { + + ValidationState state = new ValidationState(this.getNameOrAlias()); if (this.required && (getValue() == null)) { - return false; + state.addErrorMessage("Value is required and cannot be empty."); + return state; } if (this.validator != null) { for (V value : this.value) { - this.validator.accept(value); + validator.validate(value, state); } } - return true; + return state; } @Override diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java index 908d33dbb..bb1c68a12 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -3,9 +3,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.function.Consumer; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * Extends {@link FileProperty} for repository properties config file with auto-completion. @@ -30,9 +30,9 @@ public RepositoryProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public RepositoryProperty(String name, boolean required, String alias, Consumer validator) { + public RepositoryProperty(String name, boolean required, String alias, PropertyValidator validator) { super(name, required, alias, true, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java index 35e9c5bfb..b9ff9c479 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/StringProperty.java @@ -1,8 +1,7 @@ package com.devonfw.tools.ide.property; -import java.util.function.Consumer; - import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link Property} with {@link #getValueType() value type} {@link String}. @@ -40,10 +39,10 @@ public StringProperty(String name, boolean required, boolean multivalued, String * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. * @param multivalued the boolean flag about multiple arguments */ - public StringProperty(String name, boolean required, String alias, boolean multivalued, Consumer validator) { + public StringProperty(String name, boolean required, String alias, boolean multivalued, PropertyValidator validator) { super(name, required, alias, multivalued, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java index 2bca2e85e..8df35c8c1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java @@ -1,11 +1,10 @@ package com.devonfw.tools.ide.property; -import java.util.function.Consumer; - import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.validation.PropertyValidator; /** * {@link Property} with {@link #getValueType() value type} {@link ToolCommandlet}. @@ -44,9 +43,9 @@ public ToolProperty(String name, boolean required, boolean multivalued, String a * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. * @param multivalued the boolean flag about multiple arguments - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public ToolProperty(String name, boolean required, String alias, boolean multivalued, Consumer validator) { + public ToolProperty(String name, boolean required, String alias, boolean multivalued, PropertyValidator validator) { super(name, required, alias, multivalued, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java index 4db38498e..0bff2636f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/VersionProperty.java @@ -2,7 +2,6 @@ import java.util.Collections; import java.util.List; -import java.util.function.Consumer; import java.util.stream.IntStream; import com.devonfw.tools.ide.commandlet.Commandlet; @@ -10,6 +9,7 @@ import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.validation.PropertyValidator; import com.devonfw.tools.ide.version.VersionIdentifier; import com.devonfw.tools.ide.version.VersionSegment; @@ -36,9 +36,9 @@ public VersionProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + * @param validator the {@link PropertyValidator} used to {@link #validate() validate} the {@link #getValue() value}. */ - public VersionProperty(String name, boolean required, String alias, Consumer validator) { + public VersionProperty(String name, boolean required, String alias, PropertyValidator validator) { super(name, required, alias, false, validator); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/validation/PropertyValidator.java b/cli/src/main/java/com/devonfw/tools/ide/validation/PropertyValidator.java new file mode 100644 index 000000000..aafaefd03 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/validation/PropertyValidator.java @@ -0,0 +1,8 @@ +package com.devonfw.tools.ide.validation; + +@FunctionalInterface +public interface PropertyValidator { + + void validate(V value, ValidationState validationState); + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationResult.java b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationResult.java new file mode 100644 index 000000000..c9fe76643 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationResult.java @@ -0,0 +1,9 @@ +package com.devonfw.tools.ide.validation; + +public interface ValidationResult { + + boolean isValid(); + + String getErrorMessage(); + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java new file mode 100644 index 000000000..ed165da20 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java @@ -0,0 +1,50 @@ +package com.devonfw.tools.ide.validation; + +public class ValidationState implements ValidationResult { + + private StringBuilder errorMessage; + + private String propertyName; + + public ValidationState(String propertyName) { + this.propertyName = propertyName; + } + + public boolean isValid() { + return (this.errorMessage == null); + } + + public String getErrorMessage() { + if (this.errorMessage == null) { + return null; + } + return this.errorMessage.toString(); + } + + public void addErrorMessage(String error) { + if (this.errorMessage == null) { + if (this.propertyName == null) { + this.errorMessage = new StringBuilder(error.length() + 1); + this.errorMessage.append('\n'); + } else { + this.errorMessage = new StringBuilder(error.length() + propertyName.length() + 21); // 21 for the static text below + this.errorMessage.append(String.format("Error in property %s:", propertyName)); + this.errorMessage.append('\n'); + } + } else { + this.errorMessage.append('\n'); + } + this.errorMessage.append(error); + } + + public void add(ValidationResult result) { + if (!result.isValid()) { + if (this.errorMessage == null) { + this.errorMessage = new StringBuilder(result.getErrorMessage().length()); + this.errorMessage.append(result.getErrorMessage()); + } else { + addErrorMessage(result.getErrorMessage()); + } + } + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/validation/ValidationStateTest.java b/cli/src/test/java/com/devonfw/tools/ide/validation/ValidationStateTest.java new file mode 100644 index 000000000..1c6263ff6 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/validation/ValidationStateTest.java @@ -0,0 +1,70 @@ +package com.devonfw.tools.ide.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class ValidationStateTest { + + @Test + void testAddTwoResults() { + + ValidationState stateOne = new ValidationState("testPropertyOne"); + stateOne.addErrorMessage("state one: first error message"); + stateOne.addErrorMessage("state one: second error message"); + ValidationState stateTwo = new ValidationState("testPropertyTwo"); + stateTwo.addErrorMessage("state two: first error message"); + stateTwo.addErrorMessage("state two: second error message"); + + stateOne.add(stateTwo); + + assertThat(stateOne.getErrorMessage()).isEqualTo( + "Error in property testPropertyOne:" + "\n" + + "state one: first error message" + "\n" + + "state one: second error message" + "\n" + + "Error in property testPropertyTwo:" + "\n" + + "state two: first error message" + "\n" + + "state two: second error message"); + } + + @Test + void testAddEmptyResult() { + + ValidationState stateOne = new ValidationState("testPropertyOne"); + stateOne.addErrorMessage("state one: first error message"); + stateOne.addErrorMessage("state one: second error message"); + ValidationState stateTwo = new ValidationState("testPropertyTwo"); + + stateOne.add(stateTwo); + + assertThat(stateOne.getErrorMessage()).isEqualTo( + "Error in property testPropertyOne:" + "\n" + + "state one: first error message" + "\n" + + "state one: second error message"); + } + + @Test + void testAddToEmptyResult() { + ValidationState stateOne = new ValidationState("testPropertyOne"); + ValidationState stateTwo = new ValidationState("testPropertyTwo"); + stateTwo.addErrorMessage("state two: first error message"); + stateTwo.addErrorMessage("state two: second error message"); + + stateOne.add(stateTwo); + + assertThat(stateOne.getErrorMessage()).isEqualTo( + "Error in property testPropertyTwo:" + "\n" + + "state two: first error message" + "\n" + + "state two: second error message"); + } + + @Test + void testAddBothEmptyResult() { + ValidationState stateOne = new ValidationState("testPropertyOne"); + ValidationState stateTwo = new ValidationState("testPropertyTwo"); + + stateOne.add(stateTwo); + + assertThat(stateOne.getErrorMessage()).isNull(); + } +}