Browse Source

Add ValueContext to Validators

Change-Id: I2ac99adf1fdb60dc0638e6fe98c4542ebd5f21a5
tags/8.0.0.alpha6
Teemu Suo-Anttila 7 years ago
parent
commit
524a69fd2d

+ 28
- 4
server/src/main/java/com/vaadin/data/Binder.java View File

@@ -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;
}


+ 9
- 31
server/src/main/java/com/vaadin/data/Validator.java View File

@@ -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&lt;Integer&gt; 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);

+ 1
- 1
server/src/main/java/com/vaadin/data/util/converter/ValueContext.java View File

@@ -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();
}

/**

+ 2
- 1
server/src/main/java/com/vaadin/data/validator/BeanValidator.java View File

@@ -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);


+ 2
- 1
server/src/main/java/com/vaadin/data/validator/NotEmptyValidator.java View File

@@ -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 {

+ 2
- 1
server/src/main/java/com/vaadin/data/validator/NotNullValidator.java View File

@@ -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);
}

+ 2
- 1
server/src/main/java/com/vaadin/data/validator/RangeValidator.java View File

@@ -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));
}


+ 2
- 1
server/src/main/java/com/vaadin/data/validator/RegexpValidator.java View File

@@ -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));
}


+ 3
- 2
server/src/main/java/com/vaadin/data/validator/StringLengthValidator.java View File

@@ -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());
}


+ 3
- 1
server/src/main/java/com/vaadin/ui/AbstractDateField.java View File

@@ -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()));
}

+ 5
- 10
server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java View File

@@ -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);


+ 5
- 4
server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java View File

@@ -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);

+ 7
- 8
server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java View File

@@ -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);

+ 5
- 48
server/src/test/java/com/vaadin/data/ValidatorTest.java View File

@@ -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());
}
}

+ 4
- 3
server/src/test/java/com/vaadin/data/validator/NotEmptyValidatorTest.java View File

@@ -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());

+ 3
- 2
server/src/test/java/com/vaadin/data/validator/NotNullValidatorTest.java View File

@@ -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());

+ 5
- 3
server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java View File

@@ -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));
}


Loading…
Cancel
Save