Change-Id: I2ac99adf1fdb60dc0638e6fe98c4542ebd5f21a5tags/8.0.0.alpha6
@@ -708,7 +708,8 @@ public class Binder<BEAN> implements Serializable { | |||
@Override | |||
public Result<T> convertToModel(T value, ValueContext context) { | |||
Result<? super T> validationResult = validator.apply(value); | |||
Result<? super T> validationResult = validator.apply(value, | |||
context); | |||
if (validationResult.isError()) { | |||
return Result.error(validationResult.getMessage().get()); | |||
} else { | |||
@@ -1072,6 +1073,28 @@ public class Binder<BEAN> implements Serializable { | |||
return this; | |||
} | |||
/** | |||
* A convenience method to add a validator to this binder using the | |||
* {@link Validator#from(SerializablePredicate, String)} factory method. | |||
* <p> | |||
* Bean level validators are applied on the bean instance after the bean is | |||
* updated. If the validators fail, the bean instance is reverted to its | |||
* previous state. | |||
* | |||
* @see #save(Object) | |||
* @see #saveIfValid(Object) | |||
* | |||
* @param predicate | |||
* the predicate performing validation, not null | |||
* @param message | |||
* the error message to report in case validation failure | |||
* @return this binder, for chaining | |||
*/ | |||
public Binder<BEAN> withValidator(SerializablePredicate<BEAN> predicate, | |||
String message) { | |||
return withValidator(Validator.from(predicate, message)); | |||
} | |||
/** | |||
* Validates the values of all bound fields and returns the validation | |||
* status. | |||
@@ -1134,9 +1157,10 @@ public class Binder<BEAN> implements Serializable { | |||
*/ | |||
private List<Result<?>> validateBean(BEAN bean) { | |||
Objects.requireNonNull(bean, "bean cannot be null"); | |||
List<Result<?>> results = Collections.unmodifiableList( | |||
validators.stream().map(validator -> validator.apply(bean)) | |||
.collect(Collectors.toList())); | |||
List<Result<?>> results = Collections.unmodifiableList(validators | |||
.stream() | |||
.map(validator -> validator.apply(bean, new ValueContext())) | |||
.collect(Collectors.toList())); | |||
return results; | |||
} | |||
@@ -18,9 +18,9 @@ package com.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.Objects; | |||
import java.util.function.Function; | |||
import java.util.function.Predicate; | |||
import java.util.function.BiFunction; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
import com.vaadin.server.SerializablePredicate; | |||
/** | |||
@@ -48,32 +48,8 @@ import com.vaadin.server.SerializablePredicate; | |||
* @see Result | |||
*/ | |||
@FunctionalInterface | |||
public interface Validator<T> extends Function<T, Result<T>>, Serializable { | |||
/** | |||
* Returns a validator that chains this validator with the given function. | |||
* Specifically, the function may be another validator. The resulting | |||
* validator first applies this validator, and if the value passes, then the | |||
* given validator. | |||
* <p> | |||
* For instance, the following chained validator checks if a number is | |||
* between 0 and 10, inclusive: | |||
* | |||
* <pre> | |||
* Validator<Integer> v = Validator.from(num -> num >= 0, "number must be >= 0") | |||
* .chain(Validator.from(num -> num <= 10, "number must be <= 10")); | |||
* </pre> | |||
* | |||
* @param next | |||
* the validator to apply next, not null | |||
* @return a chained validator | |||
* | |||
* @see #from(Predicate, String) | |||
*/ | |||
public default Validator<T> chain(Function<T, Result<T>> next) { | |||
Objects.requireNonNull(next, "next cannot be null"); | |||
return val -> apply(val).flatMap(next); | |||
} | |||
public interface Validator<T> | |||
extends BiFunction<T, ValueContext, Result<T>>, Serializable { | |||
/** | |||
* Validates the given value. Returns a {@code Result} instance representing | |||
@@ -81,10 +57,12 @@ public interface Validator<T> extends Function<T, Result<T>>, Serializable { | |||
* | |||
* @param value | |||
* the input value to validate | |||
* @param context | |||
* the value context for validation | |||
* @return the validation result | |||
*/ | |||
@Override | |||
public Result<T> apply(T value); | |||
public Result<T> apply(T value, ValueContext context); | |||
/** | |||
* Returns a validator that passes any value. | |||
@@ -94,7 +72,7 @@ public interface Validator<T> extends Function<T, Result<T>>, Serializable { | |||
* @return an always-passing validator | |||
*/ | |||
public static <T> Validator<T> alwaysPass() { | |||
return v -> Result.ok(v); | |||
return (v, ctx) -> Result.ok(v); | |||
} | |||
/** | |||
@@ -123,7 +101,7 @@ public interface Validator<T> extends Function<T, Result<T>>, Serializable { | |||
String errorMessage) { | |||
Objects.requireNonNull(guard, "guard cannot be null"); | |||
Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); | |||
return value -> { | |||
return (value, context) -> { | |||
try { | |||
if (guard.test(value)) { | |||
return Result.ok(value); |
@@ -39,8 +39,8 @@ public class ValueContext implements Serializable { | |||
* Constructor for {@code ValueContext} without a {@code Locale}. | |||
*/ | |||
public ValueContext() { | |||
this.locale = null; | |||
this.component = null; | |||
this.locale = findLocale(); | |||
} | |||
/** |
@@ -31,6 +31,7 @@ import javax.validation.metadata.ConstraintDescriptor; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.Validator; | |||
import com.vaadin.data.util.BeanUtil; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* A {@code Validator} using the JSR-303 (javax.validation) annotation-based | |||
@@ -130,7 +131,7 @@ public class BeanValidator implements Validator<Object> { | |||
* annotation or equivalent. | |||
*/ | |||
@Override | |||
public Result<Object> apply(final Object value) { | |||
public Result<Object> apply(final Object value, ValueContext context) { | |||
Set<? extends ConstraintViolation<?>> violations = getJavaxBeanValidator() | |||
.validateValue(beanType, propertyName, value); | |||
@@ -20,6 +20,7 @@ import java.util.Objects; | |||
import com.vaadin.data.HasRequired; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.Validator; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* Simple validator to check against {@code null} value and empty {@link String} | |||
@@ -65,7 +66,7 @@ public class NotEmptyValidator<T> implements Validator<T> { | |||
} | |||
@Override | |||
public Result<T> apply(T value) { | |||
public Result<T> apply(T value, ValueContext context) { | |||
if (Objects.isNull(value) || Objects.equals(value, "")) { | |||
return Result.error(message); | |||
} else { |
@@ -19,6 +19,7 @@ package com.vaadin.data.validator; | |||
import java.util.Objects; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* This validator is used for validating properties that do not allow null | |||
@@ -41,7 +42,7 @@ public class NotNullValidator extends AbstractValidator<String> { | |||
} | |||
@Override | |||
public Result<String> apply(String value) { | |||
public Result<String> apply(String value, ValueContext context) { | |||
return Objects.isNull(value) ? Result.error(getMessage(value)) | |||
: Result.ok(value); | |||
} |
@@ -19,6 +19,7 @@ import java.util.Comparator; | |||
import java.util.Objects; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* Verifies that a value is within the given range. | |||
@@ -97,7 +98,7 @@ public class RangeValidator<T> extends AbstractValidator<T> { | |||
* behavior depends on the used comparator. | |||
*/ | |||
@Override | |||
public Result<T> apply(T value) { | |||
public Result<T> apply(T value, ValueContext context) { | |||
return toResult(value, isValid(value)); | |||
} | |||
@@ -19,6 +19,7 @@ import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* A string validator comparing the string against a Java regular expression. | |||
@@ -70,7 +71,7 @@ public class RegexpValidator extends AbstractValidator<String> { | |||
} | |||
@Override | |||
public Result<String> apply(String value) { | |||
public Result<String> apply(String value, ValueContext context) { | |||
return toResult(value, isValid(value)); | |||
} | |||
@@ -17,6 +17,7 @@ | |||
package com.vaadin.data.validator; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* Verifies that the length of a string is within the given range. | |||
@@ -49,11 +50,11 @@ public class StringLengthValidator extends AbstractValidator<String> { | |||
} | |||
@Override | |||
public Result<String> apply(String value) { | |||
public Result<String> apply(String value, ValueContext context) { | |||
if (value == null) { | |||
return toResult(value, true); | |||
} | |||
Result<?> lengthCheck = validator.apply(value.length()); | |||
Result<?> lengthCheck = validator.apply(value.length(), context); | |||
return toResult(value, !lengthCheck.isError()); | |||
} | |||
@@ -30,6 +30,7 @@ import java.util.logging.Logger; | |||
import org.jsoup.nodes.Element; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
import com.vaadin.data.validator.DateRangeValidator; | |||
import com.vaadin.event.FieldEvents.BlurEvent; | |||
import com.vaadin.event.FieldEvents.BlurListener; | |||
@@ -677,7 +678,8 @@ public abstract class AbstractDateField extends AbstractField<LocalDate> | |||
getDateOutOfRangeMessage(), | |||
getDate(getRangeStart(), getResolution()), | |||
getDate(getRangeEnd(), getResolution())); | |||
Result<LocalDate> result = validator.apply(value); | |||
Result<LocalDate> result = validator.apply(value, | |||
new ValueContext(this)); | |||
if (result.isError()) { | |||
setComponentError(new UserError(getDateOutOfRangeMessage())); | |||
} |
@@ -598,10 +598,8 @@ public class BinderBookOfVaadinTest { | |||
binder.forField(yearOfBirth) | |||
.withConverter(new StringToIntegerConverter("err")) | |||
.bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth); | |||
binder.withValidator(bean -> bean.yearOfBirth < 2000 ? Result.ok(bean) | |||
: Result.error(message)) | |||
.withValidator(bean -> bean.yearOfBirth == 2000 | |||
? Result.error(message2) : Result.ok(bean)); | |||
binder.withValidator(bean -> bean.yearOfBirth < 2000, message) | |||
.withValidator(bean -> bean.yearOfBirth != 2000, message2); | |||
binder.setBean(p); | |||
@@ -664,13 +662,10 @@ public class BinderBookOfVaadinTest { | |||
BookPerson p = new BookPerson(1500, 12); | |||
binder.forField(yearOfBirth) | |||
.withConverter(new StringToIntegerConverter("err")) | |||
.withValidator(value -> value % 2 == 0 ? Result.ok(value) | |||
: Result.error(bindingMessage)) | |||
.withValidator(value -> value % 2 == 0, bindingMessage) | |||
.bind(BookPerson::getYearOfBirth, BookPerson::setYearOfBirth); | |||
binder.withValidator(bean -> bean.yearOfBirth < 2000 ? Result.ok(bean) | |||
: Result.error(message)) | |||
.withValidator(bean -> bean.yearOfBirth == 2000 | |||
? Result.error(message2) : Result.ok(bean)); | |||
binder.withValidator(bean -> bean.yearOfBirth < 2000, message) | |||
.withValidator(bean -> bean.yearOfBirth != 2000, message2); | |||
binder.setBean(p); | |||
@@ -30,6 +30,7 @@ import org.junit.Test; | |||
import com.vaadin.data.Binder.Binding; | |||
import com.vaadin.data.util.converter.StringToIntegerConverter; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
import com.vaadin.data.validator.NotEmptyValidator; | |||
import com.vaadin.server.AbstractErrorMessage; | |||
import com.vaadin.server.ErrorMessage; | |||
@@ -92,7 +93,7 @@ public class BinderConverterValidatorTest | |||
String msg2 = "bar"; | |||
binding.withValidator(new Validator<String>() { | |||
@Override | |||
public Result<String> apply(String value) { | |||
public Result<String> apply(String value, ValueContext context) { | |||
return new SimpleResult<>(null, msg1); | |||
} | |||
}); | |||
@@ -125,7 +126,7 @@ public class BinderConverterValidatorTest | |||
// validator for Number can be used on a Double | |||
TextField salaryField = new TextField(); | |||
Validator<Number> positiveNumberValidator = value -> { | |||
Validator<Number> positiveNumberValidator = (value, context) -> { | |||
if (value.doubleValue() >= 0) { | |||
return Result.ok(value); | |||
} else { | |||
@@ -568,8 +569,8 @@ public class BinderConverterValidatorTest | |||
Binding<Person, String, String> binding = binder.forField(nameField) | |||
.withValidator(notEmpty); | |||
binding.bind(Person::getFirstName, Person::setFirstName); | |||
binder.withValidator(bean -> bean.getFirstName().contains("error") | |||
? Result.error("error") : Result.ok(bean)); | |||
binder.withValidator(bean -> !bean.getFirstName().contains("error"), | |||
"error"); | |||
Person person = new Person(); | |||
person.setFirstName(""); | |||
binder.setBean(person); |
@@ -1,12 +1,12 @@ | |||
/* | |||
* 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 | |||
@@ -27,8 +27,8 @@ import com.vaadin.data.ValidationStatus.Status; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.ui.Label; | |||
public class BinderValidationStatusTest extends | |||
BinderTestBase<Binder<Person>, Person> { | |||
public class BinderValidationStatusTest | |||
extends BinderTestBase<Binder<Person>, Person> { | |||
protected final static ValidationStatusHandler NOOP = event -> { | |||
}; | |||
@@ -307,9 +307,8 @@ public class BinderValidationStatusTest extends | |||
"Using a custom status change handler so no change should end up here"); | |||
}).bind(Person::getAge, Person::setAge); | |||
binder.withValidator( | |||
bean -> !bean.getFirstName().isEmpty() && bean.getAge() > 0 | |||
? Result.ok(bean) | |||
: Result.error("Need first name and age")); | |||
bean -> !bean.getFirstName().isEmpty() && bean.getAge() > 0, | |||
"Need first name and age"); | |||
binder.setValidationStatusHandler(r -> { | |||
statusCapture.set(r); |
@@ -20,6 +20,8 @@ import java.util.Objects; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
@@ -29,65 +31,20 @@ public class ValidatorTest { | |||
@Test | |||
public void alwaysPass() { | |||
Validator<String> alwaysPass = Validator.alwaysPass(); | |||
Result<String> result = alwaysPass.apply("foo"); | |||
Result<String> result = alwaysPass.apply("foo", new ValueContext()); | |||
Assert.assertTrue(result instanceof SimpleResult); | |||
SimpleResult<String> implRes = (SimpleResult<String>) result; | |||
Assert.assertFalse(implRes.getMessage().isPresent()); | |||
} | |||
@Test | |||
public void chain_alwaysPassAndError() { | |||
Validator<String> alwaysPass = Validator.alwaysPass(); | |||
Validator<String> chain = alwaysPass | |||
.chain(value -> Result.error("foo")); | |||
Result<String> result = chain.apply("bar"); | |||
Assert.assertTrue(result.isError()); | |||
Assert.assertEquals("foo", result.getMessage().get()); | |||
} | |||
@SuppressWarnings("serial") | |||
@Test | |||
public void chain_mixture() { | |||
Validator<String> first = new Validator<String>() { | |||
@Override | |||
public Result<String> apply(String value) { | |||
if (value == null) { | |||
return Result.error("Cannot be null"); | |||
} | |||
return Result.ok(value); | |||
} | |||
}; | |||
Validator<String> second = new Validator<String>() { | |||
@Override | |||
public Result<String> apply(String value) { | |||
if (value != null && value.isEmpty()) { | |||
return Result.error("Cannot be empty"); | |||
} | |||
return Result.ok(value); | |||
} | |||
}; | |||
Validator<String> chain = first.chain(second); | |||
Result<String> result = chain.apply("bar"); | |||
Assert.assertFalse(result.isError()); | |||
result = chain.apply(null); | |||
Assert.assertTrue(result.isError()); | |||
result = chain.apply(""); | |||
Assert.assertTrue(result.isError()); | |||
} | |||
@Test | |||
public void from() { | |||
Validator<String> validator = Validator.from(Objects::nonNull, | |||
"Cannot be null"); | |||
Result<String> result = validator.apply(null); | |||
Result<String> result = validator.apply(null, new ValueContext()); | |||
Assert.assertTrue(result.isError()); | |||
result = validator.apply(""); | |||
result = validator.apply("", new ValueContext()); | |||
Assert.assertFalse(result.isError()); | |||
} | |||
} |
@@ -19,6 +19,7 @@ import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
/** | |||
* @author Vaadin Ltd | |||
@@ -29,7 +30,7 @@ public class NotEmptyValidatorTest { | |||
@Test | |||
public void nullValueIsDisallowed() { | |||
NotEmptyValidator<String> validator = new NotEmptyValidator<>("foo"); | |||
Result<String> result = validator.apply(null); | |||
Result<String> result = validator.apply(null, new ValueContext()); | |||
Assert.assertTrue(result.isError()); | |||
Assert.assertEquals("foo", result.getMessage().get()); | |||
} | |||
@@ -37,7 +38,7 @@ public class NotEmptyValidatorTest { | |||
@Test | |||
public void emptyValueIsDisallowed() { | |||
NotEmptyValidator<String> validator = new NotEmptyValidator<>("foo"); | |||
Result<String> result = validator.apply(""); | |||
Result<String> result = validator.apply("", new ValueContext()); | |||
Assert.assertTrue(result.isError()); | |||
Assert.assertEquals("foo", result.getMessage().get()); | |||
} | |||
@@ -46,7 +47,7 @@ public class NotEmptyValidatorTest { | |||
public void nonNullValueIsAllowed() { | |||
NotEmptyValidator<Object> validator = new NotEmptyValidator<>("foo"); | |||
Object value = new Object(); | |||
Result<Object> result = validator.apply(value); | |||
Result<Object> result = validator.apply(value, new ValueContext()); | |||
Assert.assertFalse(result.isError()); | |||
result.ifOk(val -> Assert.assertEquals(value, val)); | |||
result.ifError(msg -> Assert.fail()); |
@@ -19,13 +19,14 @@ import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
public class NotNullValidatorTest { | |||
@Test | |||
public void nullValueIsDisallowed() { | |||
NotNullValidator validator = new NotNullValidator("foo"); | |||
Result<String> result = validator.apply(null); | |||
Result<String> result = validator.apply(null, new ValueContext()); | |||
Assert.assertTrue(result.isError()); | |||
Assert.assertEquals("foo", result.getMessage().get()); | |||
} | |||
@@ -33,7 +34,7 @@ public class NotNullValidatorTest { | |||
@Test | |||
public void nonNullValueIsAllowed() { | |||
NotNullValidator validator = new NotNullValidator("foo"); | |||
Result<String> result = validator.apply("bar"); | |||
Result<String> result = validator.apply("bar", new ValueContext()); | |||
Assert.assertFalse(result.isError()); | |||
result.ifOk(value -> Assert.assertEquals("bar", value)); | |||
result.ifError(msg -> Assert.fail()); |
@@ -3,18 +3,20 @@ package com.vaadin.data.validator; | |||
import org.junit.Assert; | |||
import com.vaadin.data.Validator; | |||
import com.vaadin.data.util.converter.ValueContext; | |||
public class ValidatorTestBase { | |||
protected <T> void assertPasses(T value, Validator<? super T> v) { | |||
v.apply(value).handle(val -> Assert.assertEquals(value, val), | |||
err -> Assert | |||
v.apply(value, new ValueContext()) | |||
.handle(val -> Assert.assertEquals(value, val), err -> Assert | |||
.fail(value + " should pass " + v + " but got " + err)); | |||
} | |||
protected <T> void assertFails(T value, String errorMessage, | |||
Validator<? super T> v) { | |||
v.apply(value).handle(val -> Assert.fail(value + " should fail " + v), | |||
v.apply(value, new ValueContext()).handle( | |||
val -> Assert.fail(value + " should fail " + v), | |||
err -> Assert.assertEquals(errorMessage, err)); | |||
} | |||