aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--documentation/datamodel/datamodel-forms.asciidoc34
-rw-r--r--server/src/main/java/com/vaadin/data/BeanBinder.java4
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java269
-rw-r--r--server/src/main/java/com/vaadin/data/BinderResult.java72
-rw-r--r--server/src/main/java/com/vaadin/data/BinderStatusHandler.java5
-rw-r--r--server/src/main/java/com/vaadin/data/BinderValidationStatus.java161
-rw-r--r--server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java50
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationError.java104
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationException.java60
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatus.java146
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java93
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatusHandler.java (renamed from server/src/main/java/com/vaadin/data/StatusChangeHandler.java)21
-rw-r--r--server/src/test/java/com/vaadin/data/BeanBinderTest.java7
-rw-r--r--server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java194
-rw-r--r--server/src/test/java/com/vaadin/data/BinderTest.java306
15 files changed, 886 insertions, 640 deletions
diff --git a/documentation/datamodel/datamodel-forms.asciidoc b/documentation/datamodel/datamodel-forms.asciidoc
index 62fd9df38c..62d844dee6 100644
--- a/documentation/datamodel/datamodel-forms.asciidoc
+++ b/documentation/datamodel/datamodel-forms.asciidoc
@@ -323,11 +323,11 @@ Even if the user has not edited a field, all validation error will be shown if w
binder.load(new Person());
// This will make all current validation errors visible
-List<ValidationError<?>> validationErrors = binder.validate();
+ValidationStatus<Person> status = binder.validate();
-if (!validationErrors.isEmpty()) {
+if (status.hasErrors()) {
Notification.show("Validation error count: "
- + validationErrors.size());
+ + status.getValidationErrors().size());
}
----
@@ -341,7 +341,7 @@ Handling a checked exception::
----
try {
binder.save(person);
-} catch (BindingException e) {
+} catch (ValidationException e) {
Notification.show("Validation error count: "
+ e.getValidationErrors().size());
}
@@ -566,21 +566,17 @@ We can also define our own status handler to provide a custom way of handling st
----
BinderStatusHandler defaultHandler = binder.getStatusHandler();
-binder.setStatusHandler(results -> {
- String errorMessage = results.stream()
- // Ignore helper and confirmation messages
- .filter(BinderResult::isError)
- // Ignore messages that belong to a specific field
- .filter(error -> !error.getField().isPresent())
- // Create a string out of the remaining messages
- .map(Result::getMessage).map(o -> o.get())
- .collect(Collectors.joining("\n"));
-
- formStatusLabel.setValue(errorMessage);
- formStatusLabel.setVisible(!errorMessage.isEmpty());
-
- // Let the default handler show messages for each field
- defaultHandler.accept(event);
+binder.setStatusHandler(status -> {
+ // create an error message on failed bean level validations
+ List<Result<?>> errors = status.getBeanValidationErrors();
+ String errorMessage = errors.stream().map(Result::getMessage)
+ .map(o -> o.get()).collect(Collectors.joining("\n"));
+ // show error in a label
+ formStatusLabel.setValue(errorMessage);
+ formStatusLabel.setVisible(!errorMessage.isEmpty());
+
+ // Let the default handler show messages for each field
+ defaultHandler.accept(status);
});
----
diff --git a/server/src/main/java/com/vaadin/data/BeanBinder.java b/server/src/main/java/com/vaadin/data/BeanBinder.java
index ff7e329660..cbabdb80cb 100644
--- a/server/src/main/java/com/vaadin/data/BeanBinder.java
+++ b/server/src/main/java/com/vaadin/data/BeanBinder.java
@@ -149,7 +149,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
protected BeanBindingImpl(BeanBinder<BEAN> binder,
HasValue<FIELDVALUE> field,
Converter<FIELDVALUE, TARGET> converter,
- StatusChangeHandler statusChangeHandler) {
+ ValidationStatusHandler statusChangeHandler) {
super(binder, field, converter, statusChangeHandler);
}
@@ -318,7 +318,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
@Override
protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
- StatusChangeHandler handler) {
+ ValidationStatusHandler handler) {
Objects.requireNonNull(field, "field cannot be null");
Objects.requireNonNull(converter, "converter cannot be null");
return new BeanBindingImpl<>(this, field, converter, handler);
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index af5f429252..c282a89e77 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -300,15 +300,15 @@ public class Binder<BEAN> implements Serializable {
* default behavior).
* <p>
* This is just a shorthand for
- * {@link #withStatusChangeHandler(StatusChangeHandler)} method where
- * the handler instance hides the {@code label} if there is no error and
+ * {@link #withStatusHandler(StatusChangeHandler)} method where the
+ * handler instance hides the {@code label} if there is no error and
* shows it with validation error message if validation fails. It means
* that it cannot be called after
- * {@link #withStatusChangeHandler(StatusChangeHandler)} method call or
- * {@link #withStatusChangeHandler(StatusChangeHandler)} after this
- * method call.
+ * {@link #withStatusHandler(StatusChangeHandler)} method call or
+ * {@link #withStatusHandler(StatusChangeHandler)} after this method
+ * call.
*
- * @see #withStatusChangeHandler(StatusChangeHandler)
+ * @see #withStatusHandler(StatusChangeHandler)
* @see AbstractComponent#setComponentError(ErrorMessage)
* @param label
* label to show validation status for the field
@@ -316,11 +316,10 @@ public class Binder<BEAN> implements Serializable {
*/
public default Binding<BEAN, FIELDVALUE, TARGET> withStatusLabel(
Label label) {
- return withStatusChangeHandler(event -> {
- label.setValue(event.getMessage().orElse(""));
+ return withStatusHandler(status -> {
+ label.setValue(status.getMessage().orElse(""));
// Only show the label when validation has failed
- label.setVisible(
- ValidationStatus.ERROR.equals(event.getStatus()));
+ label.setVisible(status.isError());
});
}
@@ -353,19 +352,19 @@ public class Binder<BEAN> implements Serializable {
* status change handler
* @return this binding, for chaining
*/
- public Binding<BEAN, FIELDVALUE, TARGET> withStatusChangeHandler(
- StatusChangeHandler handler);
+ public Binding<BEAN, FIELDVALUE, TARGET> withStatusHandler(
+ ValidationStatusHandler handler);
/**
- * Validates the field value and returns a {@code Result} instance
- * representing the outcome of the validation.
+ * Validates the field value and returns a {@code ValidationStatus}
+ * instance representing the outcome of the validation.
*
* @see Binder#validate()
* @see Validator#apply(Object)
*
* @return the validation result.
*/
- public Result<TARGET> validate();
+ public ValidationStatus<TARGET> validate();
}
@@ -387,7 +386,7 @@ public class Binder<BEAN> implements Serializable {
private final HasValue<FIELDVALUE> field;
private Registration onValueChange;
- private StatusChangeHandler statusChangeHandler;
+ private ValidationStatusHandler statusHandler;
private boolean isStatusHandlerChanged;
private Function<BEAN, TARGET> getter;
@@ -414,11 +413,11 @@ public class Binder<BEAN> implements Serializable {
*/
protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field,
Converter<FIELDVALUE, TARGET> converterValidatorChain,
- StatusChangeHandler statusChangeHandler) {
+ ValidationStatusHandler statusChangeHandler) {
this.field = field;
this.binder = binder;
this.converterValidatorChain = converterValidatorChain;
- this.statusChangeHandler = statusChangeHandler;
+ this.statusHandler = statusChangeHandler;
}
@Override
@@ -451,13 +450,12 @@ public class Binder<BEAN> implements Serializable {
Objects.requireNonNull(converter, "converter cannot be null");
return getBinder().createBinding(getField(),
- converterValidatorChain.chain(converter),
- statusChangeHandler);
+ converterValidatorChain.chain(converter), statusHandler);
}
@Override
- public Binding<BEAN, FIELDVALUE, TARGET> withStatusChangeHandler(
- StatusChangeHandler handler) {
+ public Binding<BEAN, FIELDVALUE, TARGET> withStatusHandler(
+ ValidationStatusHandler handler) {
checkUnbound();
Objects.requireNonNull(handler, "handler cannot be null");
if (isStatusHandlerChanged) {
@@ -465,7 +463,7 @@ public class Binder<BEAN> implements Serializable {
"A StatusChangeHandler has already been set");
}
isStatusHandlerChanged = true;
- statusChangeHandler = handler;
+ statusHandler = handler;
return this;
}
@@ -500,33 +498,31 @@ public class Binder<BEAN> implements Serializable {
private void bind(BEAN bean) {
setFieldValue(bean);
- onValueChange = getField().addValueChangeListener(e -> {
- binder.setHasChanges(true);
- storeFieldValue(bean, true);
- });
+ onValueChange = getField()
+ .addValueChangeListener(e -> handleFieldValueChange(bean));
}
@Override
- public Result<TARGET> validate() {
- BinderResult<FIELDVALUE, TARGET> bindingResult = getTargetValue();
- getBinder().getStatusHandler().accept(Arrays.asList(bindingResult));
- return bindingResult;
+ public ValidationStatus<TARGET> validate() {
+ ValidationStatus<TARGET> status = getTargetValue();
+ getBinder().getStatusHandler()
+ .accept(new BinderValidationStatus<>(getBinder(),
+ Arrays.asList(status), Collections.emptyList()));
+ return status;
}
/**
* Returns the field value run through all converters and validators,
- * but doesn't fire a {@link ValidationStatusChangeEvent status change
- * event}.
+ * but doesn't pass the {@link ValidationStatus} to any status handler.
*
* @return a result containing the validated and converted value or
* describing an error
*/
- private BinderResult<FIELDVALUE, TARGET> getTargetValue() {
+ private ValidationStatus<TARGET> getTargetValue() {
FIELDVALUE fieldValue = field.getValue();
Result<TARGET> dataValue = converterValidatorChain.convertToModel(
fieldValue, ((AbstractComponent) field).getLocale());
- return dataValue.biMap((value, message) -> new BinderResult<>(this,
- value, message));
+ return new ValidationStatus<>(this, dataValue);
}
private void unbind() {
@@ -552,44 +548,54 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Handles the value change triggered by the bound field.
+ *
+ * @param bean
+ * the new value
+ */
+ private void handleFieldValueChange(BEAN bean) {
+ binder.setHasChanges(true);
+ // store field value if valid
+ ValidationStatus<TARGET> fieldValidationStatus = storeFieldValue(
+ bean);
+ List<Result<?>> binderValidationResults;
+ // if all field level validations pass, run bean level validation
+ if (!getBinder().bindings.stream().map(BindingImpl::getTargetValue)
+ .anyMatch(ValidationStatus::isError)) {
+ binderValidationResults = binder.validateItem(bean);
+ } else {
+ binderValidationResults = Collections.emptyList();
+ }
+ binder.getStatusHandler()
+ .accept(new BinderValidationStatus<>(binder,
+ Arrays.asList(fieldValidationStatus),
+ binderValidationResults));
+ }
+
+ /**
* Saves the field value by invoking the setter function on the given
- * bean, if the value passes all registered validators. Optionally runs
- * item level validators if all field validators pass.
+ * bean, if the value passes all registered validators.
*
* @param bean
* the bean to set the property value to
- * @param runBeanLevelValidation
- * <code>true</code> to run item level validators if all
- * field validators pass, <code>false</code> to always skip
- * item level validators
*/
- private void storeFieldValue(BEAN bean,
- boolean runBeanLevelValidation) {
+ private ValidationStatus<TARGET> storeFieldValue(BEAN bean) {
assert bean != null;
+
+ ValidationStatus<TARGET> validationStatus = getTargetValue();
if (setter != null) {
- BinderResult<FIELDVALUE, TARGET> validationResult = getTargetValue();
- getBinder().getStatusHandler()
- .accept(Arrays.asList(validationResult));
- validationResult.ifOk(value -> setter.accept(bean, value));
- }
- if (runBeanLevelValidation && !getBinder().bindings.stream()
- .map(BindingImpl::getTargetValue)
- .anyMatch(Result::isError)) {
- binder.validateItem(bean);
+ validationStatus.getResult().ifPresent(result -> result
+ .ifOk(value -> setter.accept(bean, value)));
}
+ return validationStatus;
}
private void setBeanValue(BEAN bean, TARGET value) {
setter.accept(bean, value);
}
- private void fireStatusChangeEvent(Result<?> result) {
- ValidationStatusChangeEvent event = new ValidationStatusChangeEvent(
- getField(),
- result.isError() ? ValidationStatus.ERROR
- : ValidationStatus.OK,
- result.getMessage().orElse(null));
- statusChangeHandler.accept(event);
+ private void notifyStatusChangeHandler(ValidationStatus<?> status) {
+ statusHandler.accept(status);
}
}
@@ -806,9 +812,10 @@ public class Binder<BEAN> implements Serializable {
* if some of the bound field values fail to validate
*/
public void save(BEAN bean) throws ValidationException {
- List<ValidationError<?>> errors = doSaveIfValid(bean);
- if (!errors.isEmpty()) {
- throw new ValidationException(errors);
+ BinderValidationStatus<BEAN> status = doSaveIfValid(bean);
+ if (status.hasErrors()) {
+ throw new ValidationException(status.getFieldValidationErrors(),
+ status.getBeanValidationErrors());
}
}
@@ -833,7 +840,7 @@ public class Binder<BEAN> implements Serializable {
* updated, {@code false} otherwise
*/
public boolean saveIfValid(BEAN bean) {
- return doSaveIfValid(bean).isEmpty();
+ return doSaveIfValid(bean).isOk();
}
/**
@@ -845,13 +852,15 @@ public class Binder<BEAN> implements Serializable {
* @return a list of field validation errors if such occur, otherwise a list
* of bean validation errors.
*/
- private List<ValidationError<?>> doSaveIfValid(BEAN bean) {
+ private BinderValidationStatus<BEAN> doSaveIfValid(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
// First run fields level validation
- List<ValidationError<?>> errors = validateBindings();
+ List<ValidationStatus<?>> bindingStatuses = validateBindings();
// If no validation errors then update bean
- if (!errors.isEmpty()) {
- return errors;
+ if (bindingStatuses.stream().filter(ValidationStatus::isError).findAny()
+ .isPresent()) {
+ return new BinderValidationStatus<>(this, bindingStatuses,
+ Collections.emptyList());
}
// Save old bean values so we can restore them if validators fail
@@ -859,10 +868,11 @@ public class Binder<BEAN> implements Serializable {
bindings.forEach(binding -> oldValues.put(binding,
binding.convertDataToFieldType(bean)));
- bindings.forEach(binding -> binding.storeFieldValue(bean, false));
+ bindings.forEach(binding -> binding.storeFieldValue(bean));
// Now run bean level validation against the updated bean
- List<ValidationError<?>> itemValidatorErrors = validateItem(bean);
- if (!itemValidatorErrors.isEmpty()) {
+ List<Result<?>> binderResults = validateItem(bean);
+ if (binderResults.stream().filter(Result::isError).findAny()
+ .isPresent()) {
// Item validator failed, revert values
bindings.forEach((BindingImpl binding) -> binding.setBeanValue(bean,
oldValues.get(binding)));
@@ -870,7 +880,8 @@ public class Binder<BEAN> implements Serializable {
// Save successful, reset hasChanges to false
setHasChanges(false);
}
- return itemValidatorErrors;
+ return new BinderValidationStatus<>(this, bindingStatuses,
+ binderResults);
}
/**
@@ -894,68 +905,56 @@ public class Binder<BEAN> implements Serializable {
}
/**
- * Validates the values of all bound fields and returns the result of the
- * validation as a list of validation errors.
+ * Validates the values of all bound fields and returns the validation
+ * status.
* <p>
* If all field level validators pass, and {@link #bind(Object)} has been
* used to bind to an item, item level validators are run for that bean.
* Item level validators are ignored if there is no bound item or if any
* field level validator fails.
* <p>
- * Validation is successful if the returned list is empty.
*
- * @return a list of validation errors or an empty list if validation
- * succeeded
+ * @return validation status for the binder
*/
- public List<ValidationError<?>> validate() {
- List<ValidationError<?>> errors = validateBindings();
- if (!errors.isEmpty()) {
- return errors;
- }
-
- if (bean != null) {
- return validateItem(bean);
+ public BinderValidationStatus<BEAN> validate() {
+ List<ValidationStatus<?>> bindingStatuses = validateBindings();
+
+ BinderValidationStatus<BEAN> validationStatus;
+ if (bindingStatuses.stream().filter(ValidationStatus::isError).findAny()
+ .isPresent() || bean == null) {
+ validationStatus = new BinderValidationStatus<>(this,
+ bindingStatuses, Collections.emptyList());
+ } else {
+ validationStatus = new BinderValidationStatus<>(this,
+ bindingStatuses, validateItem(bean));
}
-
- return Collections.emptyList();
+ getStatusHandler().accept(validationStatus);
+ return validationStatus;
}
/**
* Validates the bindings and returns the result of the validation as a list
- * of validation errors.
- * <p>
- * If all validators pass, the resulting list is empty.
+ * of validation statuses.
* <p>
* Does not run bean validators.
- * <p>
- * All results are passed to the {@link #getStatusHandler() status change
- * handler.}
*
* @see #validateItem(Object)
*
- * @return a list of validation errors or an empty list if validation
- * succeeded
+ * @return an immutable list of validation results for bindings
*/
- private List<ValidationError<?>> validateBindings() {
- List<BinderResult<?, ?>> results = new ArrayList<>();
+ private List<ValidationStatus<?>> validateBindings() {
+ List<ValidationStatus<?>> results = new ArrayList<>();
for (BindingImpl<?, ?, ?> binding : bindings) {
results.add(binding.getTargetValue());
}
-
- getStatusHandler().accept(Collections.unmodifiableList(results));
-
- return results.stream().filter(r -> r.isError())
- .map(r -> new ValidationError<>(r.getBinding().get(),
- r.getField().get().getValue(), r.getMessage().get()))
- .collect(Collectors.toList());
+ return results;
}
/**
* Validates the {@code item} using item validators added using
* {@link #withValidator(Validator)} and returns the result of the
- * validation as a list of validation errors.
+ * validation as a list of validation results.
* <p>
- * If all validators pass, the resulting list is empty.
*
* @see #withValidator(Validator)
*
@@ -964,20 +963,12 @@ public class Binder<BEAN> implements Serializable {
* @return a list of validation errors or an empty list if validation
* succeeded
*/
- private List<ValidationError<?>> validateItem(BEAN bean) {
+ private List<Result<?>> validateItem(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
- List<BinderResult<?, ?>> results = Collections.unmodifiableList(
+ List<Result<?>> results = Collections.unmodifiableList(
validators.stream().map(validator -> validator.apply(bean))
- .map(dataValue -> dataValue.biMap(
- (value, message) -> new BinderResult<>(null,
- value, message)))
.collect(Collectors.toList()));
- getStatusHandler().accept(results);
-
- return results.stream()
- .filter(Result::isError).map(res -> new ValidationError<>(this,
- bean, res.getMessage().get()))
- .collect(Collectors.toList());
+ return results;
}
/**
@@ -1032,7 +1023,7 @@ public class Binder<BEAN> implements Serializable {
* @throws NullPointerException
* for <code>null</code> status handler
* @see #setStatusLabel(Label)
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see Binding#withStatusHandler(StatusChangeHandler)
*/
public void setStatusHandler(BinderStatusHandler statusHandler) {
Objects.requireNonNull(statusHandler, "Cannot set a null "
@@ -1077,7 +1068,7 @@ public class Binder<BEAN> implements Serializable {
*/
protected <FIELDVALUE, TARGET> BindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
- StatusChangeHandler handler) {
+ ValidationStatusHandler handler) {
return new BindingImpl<>(this, field, converter, handler);
}
@@ -1117,42 +1108,48 @@ public class Binder<BEAN> implements Serializable {
/**
* Default {@link StatusChangeHandler} functional method implementation.
*
- * @param event
- * the validation event
+ * @param status
+ * the validation status
*/
- protected void handleValidationStatusChange(
- ValidationStatusChangeEvent event) {
- HasValue<?> source = event.getSource();
+ protected void handleValidationStatusChange(ValidationStatus<?> status) {
+ HasValue<?> source = status.getField();
clearError(source);
- if (Objects.equals(ValidationStatus.ERROR, event.getStatus())) {
- handleError(source, event.getMessage().get());
+ if (status.isError()) {
+ handleError(source, status.getMessage().get());
}
}
/**
+ * Returns the bindings for this binder.
+ *
+ * @return a set of the bindings
+ */
+ protected Set<BindingImpl<BEAN, ?, ?>> getBindings() {
+ return bindings;
+ }
+
+ /**
* The default binder level status handler.
* <p>
* Passes all field related results to the Binding status handlers. All
* other status changes are displayed in the status label, if one has been
* set with {@link #setStatusLabel(Label)}.
*
- * @param results
- * a list of validation results from binding and/or item level
+ * @param binderStatus
+ * status of validation results from binding and/or item level
* validators
*/
- @SuppressWarnings("unchecked")
protected void defaultHandleBinderStatusChange(
- List<BinderResult<?, ?>> results) {
+ BinderValidationStatus<?> binderStatus) {
// let field events go to binding status handlers
- results.stream().filter(br -> br.getField().isPresent())
- .forEach(br -> ((BindingImpl<BEAN, ?, ?>) br.getBinding().get())
- .fireStatusChangeEvent(br));
+ binderStatus.getFieldValidationStatuses()
+ .forEach(status -> ((BindingImpl<?, ?, ?>) status.getBinding())
+ .notifyStatusChangeHandler(status));
// show first possible error or OK status in the label if set
if (getStatusLabel().isPresent()) {
- String statusMessage = results.stream()
- .filter(r -> !r.getField().isPresent())
- .map(Result::getMessage).map(m -> m.orElse("")).findFirst()
+ String statusMessage = binderStatus.getBeanValidationErrors()
+ .stream().findFirst().flatMap(Result::getMessage)
.orElse("");
getStatusLabel().get().setValue(statusMessage);
}
diff --git a/server/src/main/java/com/vaadin/data/BinderResult.java b/server/src/main/java/com/vaadin/data/BinderResult.java
deleted file mode 100644
index 52375b88ff..0000000000
--- a/server/src/main/java/com/vaadin/data/BinderResult.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.data;
-
-import java.util.Optional;
-
-import com.vaadin.data.Binder.Binding;
-
-/**
- * A result that keeps track of the possible binding (field) it belongs to.
- *
- * @param <FIELDVALUE>
- * the value type of the field
- * @param <VALUE>
- * the result value type and the data type of the binding, matches
- * the field type if a converter has not been set
- */
-public class BinderResult<FIELDVALUE, VALUE> extends SimpleResult<VALUE> {
-
- private final Binding<?, FIELDVALUE, VALUE> binding;
-
- /**
- * Creates a new binder result.
- *
- * @param binding
- * the binding where the result originated, may be {@code null}
- * @param value
- * the resut value, can be <code>null</code>
- * @param message
- * the error message of the result, may be {@code null}
- */
- public BinderResult(Binding<?, FIELDVALUE, VALUE> binding, VALUE value,
- String message) {
- super(value, message);
- this.binding = binding;
- }
-
- /**
- * Return the binding this result originated from, or an empty optional if
- * none.
- *
- * @return the optional binding
- */
- public Optional<Binding<?, FIELDVALUE, VALUE>> getBinding() {
- return Optional.ofNullable(binding);
- }
-
- /**
- * Return the field this result originated from, or an empty optional if
- * none.
- *
- * @return the optional field
- */
- public Optional<HasValue<FIELDVALUE>> getField() {
- return binding == null ? Optional.empty()
- : Optional.ofNullable(binding.getField());
- }
-
-} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/data/BinderStatusHandler.java b/server/src/main/java/com/vaadin/data/BinderStatusHandler.java
index 4c516a5ed6..5da5106176 100644
--- a/server/src/main/java/com/vaadin/data/BinderStatusHandler.java
+++ b/server/src/main/java/com/vaadin/data/BinderStatusHandler.java
@@ -16,7 +16,6 @@
package com.vaadin.data;
import java.io.Serializable;
-import java.util.List;
import java.util.function.Consumer;
import com.vaadin.data.Binder.Binding;
@@ -34,13 +33,13 @@ import com.vaadin.data.Binder.Binding;
*
* @see Binder#setStatusHandler(BinderStatusHandler)
* @see Binder#setStatusLabel(com.vaadin.ui.Label)
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see Binding#withStatusHandler(StatusChangeHandler)
*
* @author Vaadin Ltd
* @since 8.0
*
*/
public interface BinderStatusHandler
- extends Consumer<List<BinderResult<?, ?>>>, Serializable {
+ extends Consumer<BinderValidationStatus<?>>, Serializable {
}
diff --git a/server/src/main/java/com/vaadin/data/BinderValidationStatus.java b/server/src/main/java/com/vaadin/data/BinderValidationStatus.java
new file mode 100644
index 0000000000..f23d17e5b1
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/BinderValidationStatus.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import com.vaadin.data.Binder.Binding;
+
+/**
+ * Binder validation status change. Represents the outcome of binder level
+ * validation. Has information about the validation results for the
+ * {@link Binding#withValidator(Validator) field level} and
+ * {@link Binder#withValidator(Validator)binder level} validation.
+ * <p>
+ * Note: if there are any field level validation errors, the bean level
+ * validation is not run.
+ * <p>
+ * Use {@link Binder#setStatusHandler(BinderStatusHandler)} to handle form level
+ * validation status changes.
+ *
+ * @author Vaadin Ltd
+ *
+ * @param <BEAN>
+ * the bean type of the binder
+ *
+ * @see BinderValidationStatusHandler
+ * @see Binder#setStatusHandler(BinderStatusHandler)
+ * @see Binder#validate()
+ * @see ValidationStatus
+ *
+ * @since 8.0
+ */
+public class BinderValidationStatus<BEAN> implements Serializable {
+
+ private final Binder<BEAN> binder;
+ private final List<ValidationStatus<?>> bindingStatuses;
+ private final List<Result<?>> binderStatuses;
+
+ /**
+ * Creates a new binder validation status for the given binder and
+ * validation results.
+ *
+ * @param source
+ * the source binder
+ * @param bindingStatuses
+ * the validation results for the fields
+ * @param binderStatuses
+ * the validation results for binder level validation
+ */
+ public BinderValidationStatus(Binder<BEAN> source,
+ List<ValidationStatus<?>> bindingStatuses,
+ List<Result<?>> binderStatuses) {
+ Objects.requireNonNull(binderStatuses,
+ "binding statuses cannot be null");
+ Objects.requireNonNull(binderStatuses,
+ "binder statuses cannot be null");
+ this.binder = source;
+ this.bindingStatuses = Collections.unmodifiableList(bindingStatuses);
+ this.binderStatuses = Collections.unmodifiableList(binderStatuses);
+ }
+
+ /**
+ * Gets whether validation for the binder passed or not.
+ *
+ * @return {@code true} if validation has passed, {@code false} if not
+ */
+ public boolean isOk() {
+ return !hasErrors();
+ }
+
+ /**
+ * Gets whether the validation for the binder failed or not.
+ *
+ * @return {@code true} if validation failed, {@code false} if validation
+ * passed
+ */
+ public boolean hasErrors() {
+ return binderStatuses.stream().filter(Result::isError).findAny()
+ .isPresent()
+ || bindingStatuses.stream().filter(ValidationStatus::isError)
+ .findAny().isPresent();
+ }
+
+ /**
+ * Gets the source binder of the status.
+ *
+ * @return the source binder
+ */
+ public Binder<BEAN> getBinder() {
+ return binder;
+ }
+
+ /**
+ * Gets both field and bean level validation errors.
+ *
+ * @return a list of all validation errors
+ */
+ public List<Result<?>> getValidationErrors() {
+ ArrayList<Result<?>> errors = new ArrayList<>(getFieldValidationErrors()
+ .stream().map(s -> s.getResult().get())
+ .collect(Collectors.toList()));
+ errors.addAll(getBeanValidationErrors());
+ return errors;
+ }
+
+ /**
+ * Gets the field level validation statuses.
+ *
+ * @return the field validation statuses
+ */
+ public List<ValidationStatus<?>> getFieldValidationStatuses() {
+ return bindingStatuses;
+ }
+
+ /**
+ * Gets the bean level validation results.
+ *
+ * @return the bean level validation results
+ */
+ public List<Result<?>> getBeanValidationResults() {
+ return binderStatuses;
+ }
+
+ /**
+ * Gets the failed field level validation statuses.
+ *
+ * @return a list of failed field level validation statuses
+ */
+ public List<ValidationStatus<?>> getFieldValidationErrors() {
+ return bindingStatuses.stream().filter(ValidationStatus::isError)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Gets the failed bean level validation statuses.
+ *
+ * @return a list of failed bean level validation statuses
+ */
+ public List<Result<?>> getBeanValidationErrors() {
+ return binderStatuses.stream().filter(Result::isError)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java b/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
new file mode 100644
index 0000000000..9528c5a884
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * Handler for {@link BinderValidationStatus} changes.
+ * <p>
+ * {{@link Binder#setStatusHandler(BinderStatusHandler) Register} an instance of
+ * this class to be able to customize validation status handling.
+ * <p>
+ * The default handler will show
+ * {@link AbstractComponent#setComponentError(com.vaadin.server.ErrorMessage) an
+ * error message} for failed field validations. For bean level validation errors
+ * it will display the first error message in
+ * {@link Binder#setStatusLabel(com.vaadin.ui.Label) status label}, if one has
+ * been set.
+ *
+ * @author Vaadin Ltd
+ *
+ * @param <BEAN>
+ * the bean type of the binder
+ *
+ * @see BinderValidationStatus
+ * @see Binder#validate()
+ * @see ValidationStatus
+ *
+ * @since 8.0
+ */
+public interface BinderValidationStatusHandler<BEAN>
+ extends Consumer<BinderValidationStatus<BEAN>>, Serializable {
+
+}
diff --git a/server/src/main/java/com/vaadin/data/ValidationError.java b/server/src/main/java/com/vaadin/data/ValidationError.java
deleted file mode 100644
index 1555ecad2c..0000000000
--- a/server/src/main/java/com/vaadin/data/ValidationError.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.data;
-
-import java.io.Serializable;
-import java.util.Objects;
-import java.util.Optional;
-
-import com.vaadin.data.Binder.Binding;
-
-/**
- * Represents a validation error.
- * <p>
- * A validation error is either connected to a field validator (
- * {@link #getField()} returns a non-empty optional) or to an item level
- * validator ({@link #getField()} returns an empty optional).
- *
- * @author Vaadin Ltd
- * @since 8.0
- *
- * @param <V>
- * the value type
- */
-public class ValidationError<V> implements Serializable {
-
- /**
- * This is either a {@link Binding} or a {@link Binder}.
- */
- private final Object source;
- private final String message;
- /**
- * This is either HasValue<V> value (in case of Binding) or bean (in case of
- * Binder).
- */
- private final V value;
-
- /**
- * Creates a new instance of ValidationError using the provided source
- * ({@link Binding} or {@link Binder}), value and error message.
- *
- * @param source
- * the validated binding or the binder
- * @param value
- * the invalid value
- * @param message
- * the validation error message, not {@code null}
- */
- public ValidationError(Object source, V value, String message) {
- Objects.requireNonNull(message, "message cannot be null");
- this.source = source;
- this.message = message;
- this.value = value;
- }
-
- /**
- * Returns a reference to the validated field or an empty optional if the
- * validation was not related to a single field.
- *
- * @return the validated field or an empty optional
- */
- @SuppressWarnings({ "unchecked", "rawtypes" })
- public Optional<HasValue<V>> getField() {
- if (source instanceof Binding) {
- return Optional.of(((Binding) source).getField());
- } else {
- return Optional.empty();
- }
- }
-
- /**
- * Returns a validation error message.
- *
- * @return the validation error message
- */
- public String getMessage() {
- return message;
- }
-
- /**
- * Returns the invalid value.
- * <p>
- * This is either the field value (if the validator error comes from a field
- * binding) or the bean (for item validators).
- *
- * @return the source value
- */
- public V getValue() {
- return value;
- }
-
-}
diff --git a/server/src/main/java/com/vaadin/data/ValidationException.java b/server/src/main/java/com/vaadin/data/ValidationException.java
index 41017a8393..68f7ad9e33 100644
--- a/server/src/main/java/com/vaadin/data/ValidationException.java
+++ b/server/src/main/java/com/vaadin/data/ValidationException.java
@@ -15,39 +15,75 @@
*/
package com.vaadin.data;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Indicates validation errors in a {@link Binder} when save is requested.
- *
+ *
* @see Binder#save(Object)
- *
+ *
* @author Vaadin Ltd
* @since 8.0
*
*/
public class ValidationException extends Exception {
- private final List<ValidationError<?>> errors;
+ private final List<ValidationStatus<?>> bindingValidationErrors;
+ private final List<Result<?>> binderValidationErrors;
/**
* Constructs a new exception with validation {@code errors} list.
- *
- * @param errors
- * validation errors list
+ *
+ * @param bindingValidationErrors
+ * binding validation errors list
+ * @param binderValidationErrors
+ * binder validation errors list
*/
- public ValidationException(List<ValidationError<?>> errors) {
+ public ValidationException(
+ List<ValidationStatus<?>> bindingValidationErrors,
+ List<Result<?>> binderValidationErrors) {
super("Validation has failed for some fields");
- this.errors = Collections.unmodifiableList(errors);
+ this.bindingValidationErrors = Collections
+ .unmodifiableList(bindingValidationErrors);
+ this.binderValidationErrors = Collections
+ .unmodifiableList(binderValidationErrors);
}
/**
- * Returns the validation errors list which caused the exception.
- *
- * @return validation errors list
+ * Gets both field and bean level validation errors.
+ *
+ * @return a list of all validation errors
*/
- public List<ValidationError<?>> getValidationError() {
+ public List<Result<?>> getValidationErrors() {
+ ArrayList<Result<?>> errors = new ArrayList<>(getFieldValidationErrors()
+ .stream().map(s -> s.getResult().get())
+ .collect(Collectors.toList()));
+ errors.addAll(getBeanValidationErrors());
return errors;
}
+
+ /**
+ * Returns a list of the binding level validation errors which caused the
+ * exception, or an empty list if was caused by
+ * {@link #getBeanValidationErrors() binder level validation errors}.
+ *
+ * @return binding validation errors list
+ */
+ public List<ValidationStatus<?>> getFieldValidationErrors() {
+ return bindingValidationErrors;
+ }
+
+ /**
+ * Returns a list of the binding level validation errors which caused the
+ * exception, or an empty list if was caused by
+ * {@link #getBeanValidationErrors() binder level validation errors}.
+ *
+ * @return binder validation errors list
+ */
+ public List<Result<?>> getBeanValidationErrors() {
+ return binderValidationErrors;
+ }
}
diff --git a/server/src/main/java/com/vaadin/data/ValidationStatus.java b/server/src/main/java/com/vaadin/data/ValidationStatus.java
index 03f76c5127..7203655e7b 100644
--- a/server/src/main/java/com/vaadin/data/ValidationStatus.java
+++ b/server/src/main/java/com/vaadin/data/ValidationStatus.java
@@ -15,21 +15,147 @@
*/
package com.vaadin.data;
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.Optional;
+
import com.vaadin.data.Binder.Binding;
/**
- * Validation status.
- * <p>
- * The status is the part of {@link ValidationStatusChangeEvent} which indicate
- * whether the validation failed or not.
- *
- * @see ValidationStatusChangeEvent
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
- * @see StatusChangeHandler
+ * Represents the outcome of field level validation. Use
+ * {@link Binding#withStatusChangeHandler(ValidationStatusHandler)} to register
+ * a handler for field level validation status changes.
*
* @author Vaadin Ltd
+ *
+ * @param <TARGET>
+ * the target data type of the binding for which the validation
+ * status changed, matches the field type until a converter has been
+ * set
+ *
+ * @see Binding#withStatusChangeHandler(ValidationStatusHandler)
+ * @see Binding#validate()
+ * @see ValidationStatusHandler
+ * @see BinderValidationStatus
+ *
* @since 8.0
*/
-public enum ValidationStatus {
- OK, ERROR;
+public class ValidationStatus<TARGET> implements Serializable {
+
+ /**
+ * Status of the validation.
+ * <p>
+ * The status is the part of {@link ValidationStatus} which indicates
+ * whether the validation failed or not.
+ */
+ public enum Status {
+ /** Validation passed. */
+ OK,
+ /** Validation failed. */
+ ERROR
+ }
+
+ private final Status status;
+ private final Result<TARGET> result;
+ private final Binding<?, ?, TARGET> binding;
+
+ /**
+ * Creates a new validation status for the given binding and validation
+ * result.
+ *
+ * @param source
+ * the source binding
+ * @param result
+ * the result of the validation
+ */
+ public ValidationStatus(Binding<?, ?, TARGET> source,
+ Result<TARGET> result) {
+ this(source, result.isError() ? Status.ERROR : Status.OK, result);
+ }
+
+ /**
+ * Creates a new status change event.
+ * <p>
+ * The {@code message} must be {@code null} if the {@code status} is
+ * {@link Status#OK}.
+ *
+ * @param source
+ * field whose status has changed, not {@code null}
+ * @param status
+ * updated status value, not {@code null}
+ * @param result
+ * the related result, may be {@code null}
+ */
+ public ValidationStatus(Binding<?, ?, TARGET> source, Status status,
+ Result<TARGET> result) {
+ Objects.requireNonNull(source, "Event source may not be null");
+ Objects.requireNonNull(status, "Status may not be null");
+ if (Objects.equals(status, Status.OK) && result.isError()
+ || Objects.equals(status, Status.ERROR) && !result.isError()) {
+ throw new IllegalStateException(
+ "Invalid validation status " + status + " for given result "
+ + (result == null ? "null" : result.toString()));
+ }
+ binding = source;
+ this.status = status;
+ this.result = result;
+ }
+
+ /**
+ * Gets status of the validation.
+ *
+ * @return status
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * Gets whether the validation failed or not.
+ *
+ * @return {@code true} if validation failed, {@code false} if validation
+ * passed
+ */
+ public boolean isError() {
+ return status == Status.ERROR;
+ }
+
+ /**
+ * Gets error validation message if status is {@link Status#ERROR}.
+ *
+ * @return an optional validation error status or an empty optional if
+ * status is not an error
+ */
+ public Optional<String> getMessage() {
+ return Optional.ofNullable(result).flatMap(Result::getMessage);
+ }
+
+ /**
+ * Gets the validation result if status is either {@link Status#OK} or
+ * {@link Status#ERROR} or an empty optional if status is
+ * {@link Status#UNRESOLVED}.
+ *
+ * @return the validation result
+ */
+ public Optional<Result<TARGET>> getResult() {
+ return Optional.ofNullable(result);
+ }
+
+ /**
+ * Gets the source binding of the validation status.
+ *
+ * @return the source binding
+ */
+ public Binding<?, ?, TARGET> getBinding() {
+ return binding;
+ }
+
+ /**
+ * Gets the bound field for this status.
+ *
+ * @return the field
+ */
+ public HasValue<?> getField() {
+ return getBinding().getField();
+ }
}
diff --git a/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java b/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java
deleted file mode 100644
index e021398341..0000000000
--- a/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.data;
-
-import java.util.EventObject;
-import java.util.Objects;
-import java.util.Optional;
-
-import com.vaadin.data.Binder.Binding;
-
-/**
- * Validation status change event which is fired each time when validation is
- * done. Use {@link Binding#withStatusChangeHandler(StatusChangeHandler)} method
- * to add your validation handler to listen to the event.
- *
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
- * @see StatusChangeHandler
- *
- * @author Vaadin Ltd
- * @since 8.0
- *
- */
-public class ValidationStatusChangeEvent extends EventObject {
-
- private final ValidationStatus status;
- private final String message;
-
- /**
- * Creates a new status change event.
- * <p>
- * The {@code message} must be null if the {@code status} is
- * {@link ValidationStatus#OK}.
- *
- * @param source
- * field whose status has changed, not {@code null}
- * @param status
- * updated status value, not {@code null}
- * @param message
- * error message if status is ValidationStatus.ERROR, may be
- * {@code null}
- */
- public ValidationStatusChangeEvent(HasValue<?> source,
- ValidationStatus status, String message) {
- super(source);
- Objects.requireNonNull(source, "Event source may not be null");
- Objects.requireNonNull(status, "Status may not be null");
- if (Objects.equals(status, ValidationStatus.OK) && message != null) {
- throw new IllegalStateException(
- "Message must be null if status is not an error");
- }
- this.status = status;
- this.message = message;
- }
-
- /**
- * Returns validation status of the event.
- *
- * @return validation status
- */
- public ValidationStatus getStatus() {
- return status;
- }
-
- /**
- * Returns error validation message if status is
- * {@link ValidationStatus#ERROR}.
- *
- * @return an optional validation error status or an empty optional if
- * status is not an error
- */
- public Optional<String> getMessage() {
- return Optional.ofNullable(message);
- }
-
- @Override
- public HasValue<?> getSource() {
- return (HasValue<?>) super.getSource();
- }
-
-}
diff --git a/server/src/main/java/com/vaadin/data/StatusChangeHandler.java b/server/src/main/java/com/vaadin/data/ValidationStatusHandler.java
index 790ac74fb1..932fe64575 100644
--- a/server/src/main/java/com/vaadin/data/StatusChangeHandler.java
+++ b/server/src/main/java/com/vaadin/data/ValidationStatusHandler.java
@@ -19,22 +19,25 @@ import java.io.Serializable;
import java.util.function.Consumer;
import com.vaadin.data.Binder.Binding;
+import com.vaadin.ui.AbstractComponent;
/**
- * Validation status change handler.
+ * Handler for {@link ValidationStatus} changes.
* <p>
- * Register an instance of this class using
- * {@link Binding#withStatusChangeHandler(StatusChangeHandler) to be able to
- * listen to validation status updates.
- *
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
- * @see ValidationStatusChangeEvent
+ * {@link Binding#withStatusHandler(StatusChangeHandler) Register} an instance
+ * of this class to be able to override the default handling, which is to show
+ * {@link AbstractComponent#setComponentError(com.vaadin.server.ErrorMessage) an
+ * error message} for failed field validations.
*
* @author Vaadin Ltd
+ *
+ * @see Binding#withStatusHandler(StatusChangeHandler)
+ * @see ValidationStatus
+ *
* @since 8.0
*
*/
-public interface StatusChangeHandler
- extends Consumer<ValidationStatusChangeEvent>, Serializable {
+public interface ValidationStatusHandler
+ extends Consumer<ValidationStatus<?>>, Serializable {
}
diff --git a/server/src/test/java/com/vaadin/data/BeanBinderTest.java b/server/src/test/java/com/vaadin/data/BeanBinderTest.java
index 1788c3a9e8..84a414edf1 100644
--- a/server/src/test/java/com/vaadin/data/BeanBinderTest.java
+++ b/server/src/test/java/com/vaadin/data/BeanBinderTest.java
@@ -163,9 +163,10 @@ public class BeanBinderTest {
}
private void assertInvalid(HasValue<?> field, String message) {
- List<ValidationError<?>> errors = binder.validate();
+ BinderValidationStatus<?> status = binder.validate();
+ List<ValidationStatus<?>> errors = status.getFieldValidationErrors();
assertEquals(1, errors.size());
- assertSame(field, errors.get(0).getField().get());
- assertEquals(message, errors.get(0).getMessage());
+ assertSame(field, errors.get(0).getField());
+ assertEquals(message, errors.get(0).getMessage().get());
}
}
diff --git a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
index 6861903132..d11a0ed6da 100644
--- a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
@@ -27,12 +27,14 @@ import org.junit.Before;
import org.junit.Test;
import com.vaadin.data.Binder.Binding;
+import com.vaadin.data.ValidationStatus.Status;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.data.validator.EmailValidator;
import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
+import com.vaadin.ui.Notification;
import com.vaadin.ui.PopupDateField;
import com.vaadin.ui.Slider;
import com.vaadin.ui.TextField;
@@ -140,6 +142,30 @@ public class BinderBookOfVaadinTest {
}
@Test
+ public void loadingFromBusinessObjects() {
+ // this test is just to make sure the code snippet in the book compiles
+ binder.load(new BookPerson(1969, 50000));
+
+ BinderValidationStatus<BookPerson> status = binder.validate();
+
+ if (status.hasErrors()) {
+ Notification.show("Validation error count: "
+ + status.getValidationErrors().size());
+ }
+ }
+
+ @Test
+ public void handlingCheckedException() {
+ // another test just to verify that book examples actually compile
+ try {
+ binder.save(new BookPerson(2000, 50000));
+ } catch (ValidationException e) {
+ Notification.show("Validation error count: "
+ + e.getValidationErrors().size());
+ }
+ }
+
+ @Test
public void simpleEmailValidator() {
binder.forField(field)
// Explicit validator instance
@@ -148,16 +174,16 @@ public class BinderBookOfVaadinTest {
.bind(BookPerson::getEmail, BookPerson::setEmail);
field.setValue("not-email");
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertEquals(1, errors.size());
+ BinderValidationStatus<?> status = binder.validate();
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
Assert.assertEquals("This doesn't look like a valid email address",
- errors.get(0).getMessage());
+ status.getFieldValidationErrors().get(0).getMessage().get());
Assert.assertEquals("This doesn't look like a valid email address",
((AbstractErrorMessage) field.getErrorMessage()).getMessage());
field.setValue("abc@vaadin.com");
- errors = binder.validate();
- Assert.assertEquals(0, errors.size());
+ status = binder.validate();
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
Assert.assertNull(field.getErrorMessage());
}
@@ -170,16 +196,16 @@ public class BinderBookOfVaadinTest {
.bind(BookPerson::getLastName, BookPerson::setLastName);
field.setValue("a");
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertEquals(1, errors.size());
+ BinderValidationStatus<?> status = binder.validate();
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
Assert.assertEquals("Last name must contain at least three characters",
- errors.get(0).getMessage());
+ status.getFieldValidationErrors().get(0).getMessage().get());
Assert.assertEquals("Last name must contain at least three characters",
((AbstractErrorMessage) field.getErrorMessage()).getMessage());
field.setValue("long last name");
- errors = binder.validate();
- Assert.assertEquals(0, errors.size());
+ status = binder.validate();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
Assert.assertNull(field.getErrorMessage());
}
@@ -194,25 +220,25 @@ public class BinderBookOfVaadinTest {
.bind(BookPerson::getEmail, BookPerson::setEmail);
field.setValue("not-email");
- List<ValidationError<?>> errors = binder.validate();
+ BinderValidationStatus<?> status = binder.validate();
// Only one error per field should be reported
- Assert.assertEquals(1, errors.size());
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
Assert.assertEquals("This doesn't look like a valid email address",
- errors.get(0).getMessage());
+ status.getFieldValidationErrors().get(0).getMessage().get());
Assert.assertEquals("This doesn't look like a valid email address",
((AbstractErrorMessage) field.getErrorMessage()).getMessage());
field.setValue("abc@vaadin.com");
- errors = binder.validate();
- Assert.assertEquals(1, errors.size());
+ status = binder.validate();
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
Assert.assertEquals("Only acme.com email addresses are allowed",
- errors.get(0).getMessage());
+ status.getFieldValidationErrors().get(0).getMessage().get());
Assert.assertEquals("Only acme.com email addresses are allowed",
((AbstractErrorMessage) field.getErrorMessage()).getMessage());
field.setValue("abc@acme.com");
- errors = binder.validate();
- Assert.assertEquals(0, errors.size());
+ status = binder.validate();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
Assert.assertNull(field.getErrorMessage());
}
@@ -296,32 +322,32 @@ public class BinderBookOfVaadinTest {
departing.setValue(before);
returning.setValue(after);
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertTrue(errors.isEmpty());
+ BinderValidationStatus<Trip> status = binder.validate();
+ Assert.assertTrue(status.getBeanValidationErrors().isEmpty());
Assert.assertNull(departing.getComponentError());
Assert.assertNull(returning.getComponentError());
// update returning => validation is done against this field
returning.setValue(past);
- errors = binder.validate();
+ status = binder.validate();
- Assert.assertFalse(errors.isEmpty());
+ Assert.assertFalse(status.getFieldValidationErrors().isEmpty());
Assert.assertNotNull(returning.getComponentError());
Assert.assertNull(departing.getComponentError());
// set correct value back
returning.setValue(before);
- errors = binder.validate();
+ status = binder.validate();
- Assert.assertTrue(errors.isEmpty());
+ Assert.assertTrue(status.getFieldValidationErrors().isEmpty());
Assert.assertNull(departing.getComponentError());
Assert.assertNull(returning.getComponentError());
// update departing => validation is done because of listener added
departing.setValue(after);
- errors = binder.validate();
+ status = binder.validate();
- Assert.assertFalse(errors.isEmpty());
+ Assert.assertFalse(status.getFieldValidationErrors().isEmpty());
Assert.assertNotNull(returning.getComponentError());
Assert.assertNull(departing.getComponentError());
@@ -351,7 +377,7 @@ public class BinderBookOfVaadinTest {
departing.setValue(before);
returning.setValue(after);
- Result<Date> result = returnBinding.validate();
+ ValidationStatus<Date> result = returnBinding.validate();
Assert.assertFalse(result.isError());
Assert.assertNull(departing.getComponentError());
@@ -402,17 +428,16 @@ public class BinderBookOfVaadinTest {
@Test
public void withBindingStatusChangeHandlerExample() {
Label nameStatus = new Label();
- AtomicReference<ValidationStatusChangeEvent> event = new AtomicReference<>();
+ AtomicReference<ValidationStatus<?>> statusCapture = new AtomicReference<>();
String msg = "Full name must contain at least three characters";
binder.forField(field).withValidator(name -> name.length() >= 3, msg)
- .withStatusChangeHandler(statusChange -> {
+ .withStatusHandler(statusChange -> {
nameStatus.setValue(statusChange.getMessage().orElse(""));
// Only show the label when validation has failed
- boolean error = statusChange
- .getStatus() == ValidationStatus.ERROR;
+ boolean error = statusChange.getStatus() == Status.ERROR;
nameStatus.setVisible(error);
- event.set(statusChange);
+ statusCapture.set(statusChange);
}).bind(BookPerson::getLastName, BookPerson::setLastName);
field.setValue("aa");
@@ -420,22 +445,22 @@ public class BinderBookOfVaadinTest {
Assert.assertTrue(nameStatus.isVisible());
Assert.assertEquals(msg, nameStatus.getValue());
- Assert.assertNotNull(event.get());
- ValidationStatusChangeEvent evt = event.get();
- Assert.assertEquals(ValidationStatus.ERROR, evt.getStatus());
- Assert.assertEquals(msg, evt.getMessage().get());
- Assert.assertEquals(field, evt.getSource());
+ Assert.assertNotNull(statusCapture.get());
+ ValidationStatus<?> status = statusCapture.get();
+ Assert.assertEquals(Status.ERROR, status.getStatus());
+ Assert.assertEquals(msg, status.getMessage().get());
+ Assert.assertEquals(field, status.getField());
field.setValue("foo");
binder.validate();
Assert.assertFalse(nameStatus.isVisible());
Assert.assertEquals("", nameStatus.getValue());
- Assert.assertNotNull(event.get());
- evt = event.get();
- Assert.assertEquals(ValidationStatus.OK, evt.getStatus());
- Assert.assertFalse(evt.getMessage().isPresent());
- Assert.assertEquals(field, evt.getSource());
+ Assert.assertNotNull(statusCapture.get());
+ status = statusCapture.get();
+ Assert.assertEquals(Status.OK, status.getStatus());
+ Assert.assertFalse(status.getMessage().isPresent());
+ Assert.assertEquals(field, status.getField());
}
@Test
@@ -495,17 +520,18 @@ public class BinderBookOfVaadinTest {
.bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth);
yearOfBirthField.setValue("abc");
- Assert.assertEquals("Doesn't look like a year",
- binder.validate().get(0).getMessage());
+ Assert.assertEquals("Doesn't look like a year", binder.validate()
+ .getFieldValidationErrors().get(0).getMessage().get());
yearOfBirthField.setValue("abcd");
- Assert.assertEquals("Must enter a number",
- binder.validate().get(0).getMessage());
+ Assert.assertEquals("Must enter a number", binder.validate()
+ .getFieldValidationErrors().get(0).getMessage().get());
yearOfBirthField.setValue("1200");
Assert.assertEquals("Person must be born in the 20th century",
- binder.validate().get(0).getMessage());
+ binder.validate().getFieldValidationErrors().get(0).getMessage()
+ .get());
yearOfBirthField.setValue("1950");
- Assert.assertTrue(binder.validate().isEmpty());
+ Assert.assertFalse(binder.validate().hasErrors());
BookPerson person = new BookPerson(1500, 12);
binder.save(person);
Assert.assertEquals(1950, person.getYearOfBirth());
@@ -546,15 +572,17 @@ public class BinderBookOfVaadinTest {
binder.bind(p);
yearOfBirthField.setValue("abc");
- Assert.assertEquals("Please enter a number",
- binder.validate().get(0).getMessage());
+ Assert.assertTrue(binder.validate().hasErrors());
+ Assert.assertEquals("Please enter a number", binder.validate()
+ .getFieldValidationErrors().get(0).getMessage().get());
yearOfBirthField.setValue("123");
- Assert.assertTrue(binder.validate().isEmpty());
+ Assert.assertTrue(binder.validate().isOk());
p.setYearOfBirth(12500);
binder.load(p);
Assert.assertEquals("12500", yearOfBirthField.getValue());
+ Assert.assertTrue(binder.validate().isOk());
}
@Test
@@ -582,25 +610,30 @@ public class BinderBookOfVaadinTest {
// first bean validator fails and passes error message to status label
yearOfBirth.setValue("2001");
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertEquals(1, errors.size());
- Assert.assertEquals(errors.get(0).getMessage(), message);
+ BinderValidationStatus<?> status = binder.validate();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(1, status.getBeanValidationErrors().size());
+ Assert.assertEquals(
+ status.getBeanValidationErrors().get(0).getMessage().get(),
+ message);
Assert.assertEquals(message, formStatusLabel.getValue());
// value is correct, status label is cleared
yearOfBirth.setValue("1999");
- errors = binder.validate();
- Assert.assertEquals(0, errors.size());
+ status = binder.validate();
+ Assert.assertFalse(status.hasErrors());
Assert.assertEquals("", formStatusLabel.getValue());
// both bean validators fail, should be two error messages chained
yearOfBirth.setValue("2000");
- errors = binder.validate();
- Assert.assertEquals(2, errors.size());
+ status = binder.validate();
+ Assert.assertEquals(2, status.getBeanValidationResults().size());
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, status.getBeanValidationErrors().size());
// only first error is shown
Assert.assertEquals(message, formStatusLabel.getValue());
@@ -612,21 +645,17 @@ public class BinderBookOfVaadinTest {
BinderStatusHandler defaultHandler = binder.getStatusHandler();
- binder.setStatusHandler(results -> {
- String errorMessage = results.stream()
- // Ignore confirmation messages
- .filter(BinderResult::isError)
- // Ignore messages that belong to a specific field
- .filter(error -> !error.getField().isPresent())
- // Create a string out of the remaining messages
- .map(Result::getMessage).map(o -> o.get())
- .collect(Collectors.joining("\n"));
-
+ binder.setStatusHandler(status -> {
+ // create an error message on failed bean level validations
+ List<Result<?>> errors = status.getBeanValidationErrors();
+ String errorMessage = errors.stream().map(Result::getMessage)
+ .map(o -> o.get()).collect(Collectors.joining("\n"));
+ // show error in a label
formStatusLabel.setValue(errorMessage);
formStatusLabel.setVisible(!errorMessage.isEmpty());
// Let the default handler show messages for each field
- defaultHandler.accept(results);
+ defaultHandler.accept(status);
});
final String bindingMessage = "uneven";
@@ -648,34 +677,41 @@ public class BinderBookOfVaadinTest {
// first binding validation fails, no bean level validation is done
yearOfBirth.setValue("2001");
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertEquals(1, errors.size());
- Assert.assertEquals(errors.get(0).getMessage(), bindingMessage);
+ BinderValidationStatus<?> status = binder.validate();
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
+ Assert.assertEquals(bindingMessage,
+ status.getFieldValidationErrors().get(0).getMessage().get());
Assert.assertEquals("", formStatusLabel.getValue());
// first bean validator fails and passes error message to status label
yearOfBirth.setValue("2002");
- errors = binder.validate();
- Assert.assertEquals(1, errors.size());
- Assert.assertEquals(errors.get(0).getMessage(), message);
+ status = binder.validate();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(1, status.getBeanValidationErrors().size());
+ Assert.assertEquals(message,
+ status.getBeanValidationErrors().get(0).getMessage().get());
Assert.assertEquals(message, formStatusLabel.getValue());
// value is correct, status label is cleared
yearOfBirth.setValue("1998");
- errors = binder.validate();
- Assert.assertEquals(0, errors.size());
+ status = binder.validate();
+ Assert.assertTrue(status.isOk());
+ Assert.assertFalse(status.hasErrors());
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
Assert.assertEquals("", formStatusLabel.getValue());
// both bean validators fail, should be two error messages chained
yearOfBirth.setValue("2000");
- errors = binder.validate();
- Assert.assertEquals(2, errors.size());
+ status = binder.validate();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, status.getBeanValidationErrors().size());
Assert.assertEquals(message + "\n" + message2,
formStatusLabel.getValue());
diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java
index 4b69fdf50e..0a82b527bb 100644
--- a/server/src/test/java/com/vaadin/data/BinderTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderTest.java
@@ -5,17 +5,15 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import java.util.List;
-import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.vaadin.data.Binder.Binding;
+import com.vaadin.data.ValidationStatus.Status;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.validator.NotEmptyValidator;
import com.vaadin.server.AbstractErrorMessage;
@@ -27,6 +25,8 @@ import com.vaadin.ui.TextField;
public class BinderTest {
+ private static final String EMPTY_ERROR_MESSAGE = "Value cannot be empty";
+
private static class StatusBean {
private String status;
@@ -48,7 +48,7 @@ public class BinderTest {
Person p = new Person();
Validator<String> notEmpty = Validator.from(val -> !val.isEmpty(),
- "Value cannot be empty");
+ EMPTY_ERROR_MESSAGE);
Converter<String, Integer> stringToInteger = Converter.from(
Integer::valueOf, String::valueOf, e -> "Value must be a number");
Validator<Integer> notNegative = Validator.from(x -> x >= 0,
@@ -327,18 +327,17 @@ public class BinderTest {
binder.save(person);
Assert.fail();
} catch (ValidationException exception) {
- List<ValidationError<?>> validationErrors = exception
- .getValidationError();
+ List<ValidationStatus<?>> validationErrors = exception
+ .getFieldValidationErrors();
Assert.assertEquals(2, validationErrors.size());
- ValidationError<?> error = validationErrors.get(0);
- Assert.assertEquals(nameField, error.getField().get());
- Assert.assertEquals(msg, error.getMessage());
- Assert.assertEquals("", error.getValue());
+ ValidationStatus<?> error = validationErrors.get(0);
+ Assert.assertEquals(nameField, error.getField());
+ Assert.assertEquals(msg, error.getMessage().get());
error = validationErrors.get(1);
- Assert.assertEquals(ageField, error.getField().get());
- Assert.assertEquals("Value must be positive", error.getMessage());
- Assert.assertEquals(ageField.getValue(), error.getValue());
+ Assert.assertEquals(ageField, error.getField());
+ Assert.assertEquals("Value must be positive",
+ error.getMessage().get());
}
}
@@ -375,9 +374,9 @@ public class BinderTest {
public void validate_notBound_noErrors() {
Binder<Person> binder = new Binder<>();
- List<ValidationError<?>> errors = binder.validate();
+ BinderValidationStatus<Person> status = binder.validate();
- Assert.assertTrue(errors.isEmpty());
+ Assert.assertTrue(status.isOk());
}
@Test
@@ -388,9 +387,9 @@ public class BinderTest {
Person::setFirstName);
nameField.setComponentError(new UserError(""));
- List<ValidationError<?>> errors = binder.validate();
+ BinderValidationStatus<Person> status = binder.validate();
- Assert.assertTrue(errors.isEmpty());
+ Assert.assertTrue(status.isOk());
Assert.assertNull(nameField.getComponentError());
}
@@ -411,19 +410,19 @@ public class BinderTest {
binding.withValidator(value -> false, msg2);
binding.bind(Person::getFirstName, Person::setFirstName);
- List<ValidationError<?>> errors = binder.validate();
+ BinderValidationStatus<Person> status = binder.validate();
+ List<ValidationStatus<?>> errors = status.getFieldValidationErrors();
Assert.assertEquals(1, errors.size());
- Set<String> errorMessages = errors.stream()
- .map(ValidationError::getMessage).collect(Collectors.toSet());
- Assert.assertTrue(errorMessages.contains(msg1));
+ ValidationStatus<?> validationStatus = errors.stream().findFirst()
+ .get();
+ String msg = validationStatus.getMessage().get();
+ Assert.assertEquals(msg1, msg);
+
+ HasValue<?> field = validationStatus.getField();
- Set<?> fields = errors.stream().map(ValidationError::getField)
- .filter(Optional::isPresent).map(Optional::get)
- .collect(Collectors.toSet());
- Assert.assertEquals(1, fields.size());
- Assert.assertTrue(fields.contains(nameField));
+ Assert.assertEquals(nameField, field);
ErrorMessage componentError = nameField.getComponentError();
Assert.assertNotNull(componentError);
@@ -490,19 +489,25 @@ public class BinderTest {
ageField.setValue("");
assertEquals(32, p.getAge());
- assertValidationErrors(binder.validate(), "Value cannot be empty");
+ assertValidationErrors(binder.validate(), EMPTY_ERROR_MESSAGE);
}
private void assertValidationErrors(
- List<ValidationError<?>> validationErrors,
+ List<ValidationStatus<?>> validationErrors,
String... errorMessages) {
Assert.assertEquals(errorMessages.length, validationErrors.size());
for (int i = 0; i < errorMessages.length; i++) {
Assert.assertEquals(errorMessages[i],
- validationErrors.get(i).getMessage());
+ validationErrors.get(i).getMessage().get());
}
}
+ private void assertValidationErrors(BinderValidationStatus<Person> status,
+ String... errorMessages) {
+ assertValidationErrors(status.getFieldValidationErrors(),
+ errorMessages);
+ }
+
@Test
public void convertToModelConversionFails() {
bindAgeWithValidatorConverterValidator();
@@ -571,11 +576,11 @@ public class BinderTest {
@Test
public void bindingWithStatusChangeHandler_handlerGetsEvents() {
- AtomicReference<ValidationStatusChangeEvent> event = new AtomicReference<>();
+ AtomicReference<ValidationStatus<?>> statusCapture = new AtomicReference<>();
Binding<Person, String, String> binding = binder.forField(nameField)
- .withValidator(notEmpty).withStatusChangeHandler(evt -> {
- Assert.assertNull(event.get());
- event.set(evt);
+ .withValidator(notEmpty).withStatusHandler(evt -> {
+ Assert.assertNull(statusCapture.get());
+ statusCapture.set(evt);
});
binding.bind(Person::getFirstName, Person::setFirstName);
@@ -585,30 +590,30 @@ public class BinderTest {
// message
binder.validate();
- Assert.assertNotNull(event.get());
- ValidationStatusChangeEvent evt = event.get();
- Assert.assertEquals(ValidationStatus.ERROR, evt.getStatus());
- Assert.assertEquals("Value cannot be empty", evt.getMessage().get());
- Assert.assertEquals(nameField, evt.getSource());
+ Assert.assertNotNull(statusCapture.get());
+ ValidationStatus<?> evt = statusCapture.get();
+ Assert.assertEquals(Status.ERROR, evt.getStatus());
+ Assert.assertEquals(EMPTY_ERROR_MESSAGE, evt.getMessage().get());
+ Assert.assertEquals(nameField, evt.getField());
nameField.setValue("foo");
- event.set(null);
+ statusCapture.set(null);
// Second validation succeeds => should be event with OK status and
// no message
binder.validate();
- evt = event.get();
+ evt = statusCapture.get();
Assert.assertNotNull(evt);
- Assert.assertEquals(ValidationStatus.OK, evt.getStatus());
+ Assert.assertEquals(Status.OK, evt.getStatus());
Assert.assertFalse(evt.getMessage().isPresent());
- Assert.assertEquals(nameField, evt.getSource());
+ Assert.assertEquals(nameField, evt.getField());
}
@Test
public void bindingWithStatusChangeHandler_defaultStatusChangeHandlerIsReplaced() {
Binding<Person, String, String> binding = binder.forField(nameField)
- .withValidator(notEmpty).withStatusChangeHandler(evt -> {
+ .withValidator(notEmpty).withStatusHandler(evt -> {
});
binding.bind(Person::getFirstName, Person::setFirstName);
@@ -639,7 +644,7 @@ public class BinderTest {
binding.validate();
Assert.assertTrue(label.isVisible());
- Assert.assertEquals("Value cannot be empty", label.getValue());
+ Assert.assertEquals(EMPTY_ERROR_MESSAGE, label.getValue());
nameField.setValue("foo");
@@ -677,7 +682,7 @@ public class BinderTest {
.withValidator(notEmpty);
binding.bind(Person::getFirstName, Person::setFirstName);
- binding.withStatusChangeHandler(evt -> Assert.fail());
+ binding.withStatusHandler(evt -> Assert.fail());
}
@Test(expected = IllegalStateException.class)
@@ -697,7 +702,7 @@ public class BinderTest {
Binding<Person, String, String> binding = binder.forField(nameField);
- binding.withStatusChangeHandler(event -> {
+ binding.withStatusHandler(event -> {
});
binding.withStatusLabel(label);
@@ -711,7 +716,7 @@ public class BinderTest {
binding.withStatusLabel(label);
- binding.withStatusChangeHandler(event -> {
+ binding.withStatusHandler(event -> {
});
}
@@ -720,10 +725,10 @@ public class BinderTest {
Binding<Person, String, String> binding = binder.forField(nameField);
- binding.withStatusChangeHandler(event -> {
+ binding.withStatusHandler(event -> {
});
- binding.withStatusChangeHandler(event -> {
+ binding.withStatusHandler(event -> {
});
}
@@ -738,9 +743,9 @@ public class BinderTest {
Person person = new Person();
binder.bind(person);
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertEquals(1, errors.size());
- Assert.assertFalse(errors.get(0).getField().isPresent());
+ List<ValidationStatus<?>> errors = binder.validate()
+ .getFieldValidationErrors();
+ Assert.assertEquals(0, errors.size());
}
@Test
@@ -756,12 +761,12 @@ public class BinderTest {
Person person = new Person();
binder.bind(person);
- List<ValidationError<?>> errors = binder.validate();
+ List<ValidationStatus<?>> errors = binder.validate()
+ .getFieldValidationErrors();
Assert.assertEquals(1, errors.size());
- ValidationError<?> error = errors.get(0);
- Assert.assertEquals(msg, error.getMessage());
- Assert.assertTrue(error.getField().isPresent());
- Assert.assertEquals(nameField.getValue(), error.getValue());
+ ValidationStatus<?> error = errors.get(0);
+ Assert.assertEquals(msg, error.getMessage().get());
+ Assert.assertEquals(nameField, error.getField());
}
@Test
@@ -778,14 +783,14 @@ public class BinderTest {
Person person = new Person();
binder.bind(person);
- List<ValidationError<?>> errors = binder.validate();
+ List<ValidationStatus<?>> errors = binder.validate()
+ .getFieldValidationErrors();
Assert.assertEquals(1, errors.size());
- ValidationError<?> error = errors.get(0);
+ ValidationStatus<?> error = errors.get(0);
- Assert.assertEquals(msg1, error.getMessage());
- Assert.assertEquals(nameField, error.getField().get());
- Assert.assertEquals(nameField.getValue(), error.getValue());
+ Assert.assertEquals(msg1, error.getMessage().get());
+ Assert.assertEquals(nameField, error.getField());
}
@Test
@@ -799,8 +804,8 @@ public class BinderTest {
Person person = new Person();
binder.bind(person);
- List<ValidationError<?>> errors = binder.validate();
- Assert.assertEquals(0, errors.size());
+ Assert.assertFalse(binder.validate().hasErrors());
+ Assert.assertTrue(binder.validate().isOk());
}
@Test
@@ -863,15 +868,105 @@ public class BinderTest {
}
@Test
- public void binderWithStatusChangeHandler_handlerGetsEvents() {
- AtomicReference<List<BinderResult<?, ?>>> resultsCapture = new AtomicReference<>();
+ public void binderWithStatusHandler_fieldValidationNoBeanValidation_handlerGetsStatusUpdates() {
+ AtomicReference<BinderValidationStatus<?>> statusCapture = new AtomicReference<>();
binder.forField(nameField).withValidator(notEmpty)
- .withStatusChangeHandler(evt -> {
+ .withStatusHandler(evt -> {
Assert.fail(
"Using a custom status change handler so no change should end up here");
}).bind(Person::getFirstName, Person::setFirstName);
binder.forField(ageField).withConverter(stringToInteger)
- .withValidator(notNegative).withStatusChangeHandler(evt -> {
+ .withValidator(notNegative).withStatusHandler(evt -> {
+ Assert.fail(
+ "Using a custom status change handler so no change should end up here");
+ }).bind(Person::getAge, Person::setAge);
+
+ binder.setStatusHandler(r -> {
+ statusCapture.set(r);
+ });
+ binder.bind(p);
+ Assert.assertNull(nameField.getComponentError());
+
+ nameField.setValue("");
+ ageField.setValue("5");
+
+ // First binding validation fails => should be result with ERROR status
+ // and message
+ BinderValidationStatus<Person> status2 = binder.validate();
+ BinderValidationStatus<?> status = statusCapture.get();
+ Assert.assertSame(status2, status);
+
+ Assert.assertNull(nameField.getComponentError());
+
+ List<ValidationStatus<?>> bindingStatuses = status
+ .getFieldValidationStatuses();
+ Assert.assertNotNull(bindingStatuses);
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, bindingStatuses.size());
+
+ ValidationStatus<?> r = bindingStatuses.get(0);
+ Assert.assertTrue(r.isError());
+ Assert.assertEquals(EMPTY_ERROR_MESSAGE, r.getMessage().get());
+ Assert.assertEquals(nameField, r.getField());
+
+ r = bindingStatuses.get(1);
+ Assert.assertFalse(r.isError());
+ Assert.assertFalse(r.getMessage().isPresent());
+ Assert.assertEquals(ageField, r.getField());
+
+ Assert.assertEquals(0, status.getBeanValidationResults().size());
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
+
+ nameField.setValue("foo");
+ ageField.setValue("");
+
+ statusCapture.set(null);
+ // Second validation succeeds => should be result with OK status and
+ // no message, and error result for age
+ binder.validate();
+
+ status = statusCapture.get();
+ bindingStatuses = status.getFieldValidationStatuses();
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, bindingStatuses.size());
+
+ r = bindingStatuses.get(0);
+ Assert.assertFalse(r.isError());
+ Assert.assertFalse(r.getMessage().isPresent());
+ Assert.assertEquals(nameField, r.getField());
+
+ r = bindingStatuses.get(1);
+ Assert.assertTrue(r.isError());
+ Assert.assertEquals("Value must be a number", r.getMessage().get());
+ Assert.assertEquals(ageField, r.getField());
+
+ Assert.assertEquals(0, status.getBeanValidationResults().size());
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
+
+ statusCapture.set(null);
+ // binding validations pass, binder validation fails
+ ageField.setValue("0");
+ binder.validate();
+
+ status = statusCapture.get();
+ bindingStatuses = status.getFieldValidationStatuses();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, bindingStatuses.size());
+
+ Assert.assertEquals(0, status.getBeanValidationResults().size());
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
+ }
+
+ @Test
+ public void binderWithStatusHandler_fieldAndBeanLevelValidation_handlerGetsStatusUpdates() {
+ AtomicReference<BinderValidationStatus<?>> statusCapture = new AtomicReference<>();
+ binder.forField(nameField).withValidator(notEmpty)
+ .withStatusHandler(evt -> {
+ Assert.fail(
+ "Using a custom status change handler so no change should end up here");
+ }).bind(Person::getFirstName, Person::setFirstName);
+ binder.forField(ageField).withConverter(stringToInteger)
+ .withValidator(notNegative).withStatusHandler(evt -> {
Assert.fail(
"Using a custom status change handler so no change should end up here");
}).bind(Person::getAge, Person::setAge);
@@ -881,7 +976,7 @@ public class BinderTest {
: Result.error("Need first name and age"));
binder.setStatusHandler(r -> {
- resultsCapture.set(r);
+ statusCapture.set(r);
});
binder.bind(p);
Assert.assertNull(nameField.getComponentError());
@@ -891,65 +986,78 @@ public class BinderTest {
// First binding validation fails => should be result with ERROR status
// and message
- binder.validate();
+ BinderValidationStatus<Person> status2 = binder.validate();
+ BinderValidationStatus<?> status = statusCapture.get();
+ Assert.assertSame(status2, status);
Assert.assertNull(nameField.getComponentError());
- List<BinderResult<?, ?>> results = resultsCapture.get();
- Assert.assertNotNull(results);
- Assert.assertEquals(2, results.size());
+ List<ValidationStatus<?>> bindingStatuses = status
+ .getFieldValidationStatuses();
+ Assert.assertNotNull(bindingStatuses);
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, bindingStatuses.size());
- BinderResult<?, ?> r = results.get(0);
+ ValidationStatus<?> r = bindingStatuses.get(0);
Assert.assertTrue(r.isError());
- Assert.assertEquals("Value cannot be empty", r.getMessage().get());
- Assert.assertEquals(nameField, r.getField().get());
+ Assert.assertEquals(EMPTY_ERROR_MESSAGE, r.getMessage().get());
+ Assert.assertEquals(nameField, r.getField());
- r = results.get(1);
+ r = bindingStatuses.get(1);
Assert.assertFalse(r.isError());
Assert.assertFalse(r.getMessage().isPresent());
- Assert.assertEquals(ageField, r.getField().get());
+ Assert.assertEquals(ageField, r.getField());
+
+ Assert.assertEquals(0, status.getBeanValidationResults().size());
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
nameField.setValue("foo");
ageField.setValue("");
- resultsCapture.set(null);
+ statusCapture.set(null);
// Second validation succeeds => should be result with OK status and
// no message, and error result for age
binder.validate();
- results = resultsCapture.get();
- Assert.assertNotNull(results);
- Assert.assertEquals(2, results.size());
+ status = statusCapture.get();
+ bindingStatuses = status.getFieldValidationStatuses();
+ Assert.assertEquals(1, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, bindingStatuses.size());
- r = results.get(0);
+ r = bindingStatuses.get(0);
Assert.assertFalse(r.isError());
Assert.assertFalse(r.getMessage().isPresent());
- Assert.assertEquals(nameField, r.getField().get());
+ Assert.assertEquals(nameField, r.getField());
- r = results.get(1);
+ r = bindingStatuses.get(1);
Assert.assertTrue(r.isError());
Assert.assertEquals("Value must be a number", r.getMessage().get());
- Assert.assertEquals(ageField, r.getField().get());
+ Assert.assertEquals(ageField, r.getField());
+
+ Assert.assertEquals(0, status.getBeanValidationResults().size());
+ Assert.assertEquals(0, status.getBeanValidationErrors().size());
- resultsCapture.set(null);
+ statusCapture.set(null);
// binding validations pass, binder validation fails
ageField.setValue("0");
binder.validate();
- results = resultsCapture.get();
- Assert.assertNotNull(results);
- Assert.assertEquals(1, results.size());
+ status = statusCapture.get();
+ bindingStatuses = status.getFieldValidationStatuses();
+ Assert.assertEquals(0, status.getFieldValidationErrors().size());
+ Assert.assertEquals(2, bindingStatuses.size());
- r = results.get(0);
- Assert.assertTrue(r.isError());
- Assert.assertTrue(r.getMessage().isPresent());
- Assert.assertFalse(r.getField().isPresent());
+ Assert.assertEquals(1, status.getBeanValidationResults().size());
+ Assert.assertEquals(1, status.getBeanValidationErrors().size());
+
+ Assert.assertEquals("Need first name and age",
+ status.getBeanValidationErrors().get(0).getMessage().get());
}
@Test
public void binderWithStatusChangeHandler_defaultStatusChangeHandlerIsReplaced() {
Binding<Person, String, String> binding = binder.forField(nameField)
- .withValidator(notEmpty).withStatusChangeHandler(evt -> {
+ .withValidator(notEmpty).withStatusHandler(evt -> {
});
binding.bind(Person::getFirstName, Person::setFirstName);
@@ -991,7 +1099,7 @@ public class BinderTest {
.withValidator(notEmpty);
binding.bind(Person::getFirstName, Person::setFirstName);
- binding.withStatusChangeHandler(evt -> Assert.fail());
+ binding.withStatusHandler(evt -> Assert.fail());
}
@Test(expected = IllegalStateException.class)
@@ -1038,7 +1146,7 @@ public class BinderTest {
@Test
public void binderWithStatusChangeHandler_replaceHandler() {
- AtomicReference<List<BinderResult<?, ?>>> capture = new AtomicReference<>();
+ AtomicReference<BinderValidationStatus<?>> capture = new AtomicReference<>();
Binding<Person, String, String> binding = binder.forField(nameField);
binding.bind(Person::getFirstName, Person::setFirstName);
@@ -1054,9 +1162,11 @@ public class BinderTest {
nameField.setValue("foo");
binder.validate();
- List<BinderResult<?, ?>> results = capture.get();
+ List<ValidationStatus<?>> results = capture.get()
+ .getFieldValidationStatuses();
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
+ Assert.assertFalse(results.get(0).isError());
}
@Test