]> source.dussan.org Git - vaadin-framework.git/commitdiff
Add ValueContext to Validators
authorTeemu Suo-Anttila <teemusa@vaadin.com>
Tue, 25 Oct 2016 13:28:15 +0000 (16:28 +0300)
committerTeemu Suo-Anttila <teemusa@vaadin.com>
Wed, 26 Oct 2016 13:12:26 +0000 (16:12 +0300)
Change-Id: I2ac99adf1fdb60dc0638e6fe98c4542ebd5f21a5

17 files changed:
server/src/main/java/com/vaadin/data/Binder.java
server/src/main/java/com/vaadin/data/Validator.java
server/src/main/java/com/vaadin/data/util/converter/ValueContext.java
server/src/main/java/com/vaadin/data/validator/BeanValidator.java
server/src/main/java/com/vaadin/data/validator/NotEmptyValidator.java
server/src/main/java/com/vaadin/data/validator/NotNullValidator.java
server/src/main/java/com/vaadin/data/validator/RangeValidator.java
server/src/main/java/com/vaadin/data/validator/RegexpValidator.java
server/src/main/java/com/vaadin/data/validator/StringLengthValidator.java
server/src/main/java/com/vaadin/ui/AbstractDateField.java
server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
server/src/test/java/com/vaadin/data/BinderConverterValidatorTest.java
server/src/test/java/com/vaadin/data/BinderValidationStatusTest.java
server/src/test/java/com/vaadin/data/ValidatorTest.java
server/src/test/java/com/vaadin/data/validator/NotEmptyValidatorTest.java
server/src/test/java/com/vaadin/data/validator/NotNullValidatorTest.java
server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java

index effd315ab3ae49d3eaf3e486645bf107530b5156..f500ef9a0a920ed7dcfda9019b394aac0d8ce390 100644 (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;
     }
 
index 4d4a19fbe8b3e41523ab2cc6bcda7bf538785ab9..6f1d5827ec1fd37fdecfb70638af4e4caab2e88a 100644 (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);
index a8fdf296f56b39b3460b58639a70e5b9845f4342..d478692b4fd4fac98b3f12466310e5a02ae4425c 100644 (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();
     }
 
     /**
index 9ac9c9a649a683a7965a0c8b33755cdaeb736f12..170f5a5f04f63918df8aa03a4dcd196ffadbea84 100644 (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);
 
index 0bd989bf6670634ad3f6ed12062d66123c72542f..99532c34d1e5d3bd2d3d17b04cef86f01e5b396f 100644 (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 {
index b5f33c04c89a7897aa2ef54abc9ba78ce115dbf1..349e000c68d5359993b5c01f20853bf51c41c810 100644 (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);
     }
index a6a094603518995b077e5109710d3b1fee4f5040..32effdd8116a6650f938d122df2533ee2d581c45 100644 (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));
     }
 
index f72764a860f9c6493e914bfb4ac434bc4d48bffb..b9763e5199ebdde4a905126eb2306281af8ff482 100644 (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));
     }
 
index e1d07f9ec3c36b07ff3ef911e39269a9d4a28d5a..421cf13a2993b9f6f79001cf15ad45e141f8d569 100644 (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());
     }
 
index c62c76ac9c528d3c1a8dedd094ac2023288326dd..bb90ce53be58f7a327be08dccd04e30cc659b3aa 100644 (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()));
             }
index e8007d5f1d354f93adebbee9d64a0503eff382a4..9c0ffd31e7e224592fa88875871f26cfc58d873a 100644 (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);
 
index d03f81e87303ef41dc23e90c61a372fc94997704..a0fc5cc6fa5d8dcf1d3f0411d7bb9bed9ced19a0 100644 (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);
index e85132fef3ceb642c2ebb1ed325148a76b506030..9066bf4dbeacb679c145ebf53fe051f7ef0a0c3e 100644 (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);
index d3f7fc23dedc73b75553b0aeb5ae53d5a886e9a3..ead9991b4c122348dda14a1a8e494e69fe6480c0 100644 (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());
     }
 }
index 78101b898ed1d7fbda6a7f94de36eb92e4f84cbb..85e43d2c65a9e35679c0f04ba4c1ea26134be6cd 100644 (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());
index 8762e74fa39cb19fe8b2d99dc9828197713f9b67..e3e76fbf536945314de6a19d5410d4aa80779044 100644 (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());
index 2845fda5c04d79542bce5967b13c47bccb7bba75..3d6cd2afe1604ed2f4a818843794ca027167d924 100644 (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));
     }