From 855ec0f67951da7f4392ae704796340e1d1a3ff3 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Tue, 1 Nov 2016 11:42:15 +0200 Subject: Add error message provider to provide translations Change-Id: I657535d377c471369e8c77fa1db946c490023939 --- server/src/main/java/com/vaadin/data/Binder.java | 65 +++++++++++++++++++++- .../java/com/vaadin/data/ErrorMessageProvider.java | 42 ++++++++++++++ .../src/main/java/com/vaadin/data/Validator.java | 26 ++++++++- .../test/java/com/vaadin/data/ValidatorTest.java | 30 +++++++++- .../vaadin/data/validator/ValidatorTestBase.java | 19 ++++++- 5 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 server/src/main/java/com/vaadin/data/ErrorMessageProvider.java diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index f500ef9a0a..e139bcd5c8 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -149,6 +149,9 @@ public class Binder implements Serializable { * property. If any validator returns a failure, the property value is * not updated. * + * @see #withValidator(SerializablePredicate, String) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider) + * * @param validator * the validator to add, not null * @return this binding, for chaining @@ -167,6 +170,7 @@ public class Binder implements Serializable { * failure, the property value is not updated. * * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider) * @see Validator#from(SerializablePredicate, String) * * @param predicate @@ -183,6 +187,34 @@ public class Binder implements Serializable { return withValidator(Validator.from(predicate, message)); } + /** + * A convenience method to add a validator to this binding using the + * {@link Validator#from(SerializablePredicate, ErrorMessageProvider)} + * factory method. + *

+ * Validators are applied, in registration order, when the field value + * is written to the backing property. If any validator returns a + * failure, the property value is not updated. + * + * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, String) + * @see Validator#from(SerializablePredicate, ErrorMessageProvider) + * + * @param predicate + * the predicate performing validation, not null + * @param errorMessageProvider + * the provider to generate error messages, not null + * @return this binding, for chaining + * @throws IllegalStateException + * if {@code bind} has already been called + */ + public default Binding withValidator( + SerializablePredicate predicate, + ErrorMessageProvider errorMessageProvider) { + return withValidator( + Validator.from(predicate, errorMessageProvider)); + } + /** * Maps the binding to another data type using the given * {@link Converter}. @@ -1062,6 +1094,8 @@ public class Binder implements Serializable { * * @see #writeBean(Object) * @see #writeBeanIfValid(Object) + * @see #withValidator(SerializablePredicate, String) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider) * * @param validator * the validator to add, not null @@ -1081,8 +1115,10 @@ public class Binder implements Serializable { * updated. If the validators fail, the bean instance is reverted to its * previous state. * - * @see #save(Object) - * @see #saveIfValid(Object) + * @see #writeBean(Object) + * @see #writeBeanIfValid(Object) + * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, ErrorMessageProvider) * * @param predicate * the predicate performing validation, not null @@ -1095,6 +1131,31 @@ public class Binder implements Serializable { return withValidator(Validator.from(predicate, message)); } + /** + * A convenience method to add a validator to this binder using the + * {@link Validator#from(SerializablePredicate, ErrorMessageProvider)} + * factory method. + *

+ * 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 #writeBean(Object) + * @see #writeBeanIfValid(Object) + * @see #withValidator(Validator) + * @see #withValidator(SerializablePredicate, String) + * + * @param predicate + * the predicate performing validation, not null + * @param errorMessageProvider + * the provider to generate error messages, not null + * @return this binder, for chaining + */ + public Binder withValidator(SerializablePredicate predicate, + ErrorMessageProvider errorMessageProvider) { + return withValidator(Validator.from(predicate, errorMessageProvider)); + } + /** * Validates the values of all bound fields and returns the validation * status. diff --git a/server/src/main/java/com/vaadin/data/ErrorMessageProvider.java b/server/src/main/java/com/vaadin/data/ErrorMessageProvider.java new file mode 100644 index 0000000000..71a6723634 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/ErrorMessageProvider.java @@ -0,0 +1,42 @@ +/* + * 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 com.vaadin.data.util.converter.ValueContext; +import com.vaadin.server.SerializableFunction; + +/** + * Provider interface for generating localizable error messages using + * {@link ValueContext}. + * + * @since + * @author Vaadin Ltd. + */ +@FunctionalInterface +public interface ErrorMessageProvider + extends SerializableFunction { + + /** + * Returns a generated error message for given {@code ValueContext}. + * + * @param context + * the value context + * + * @return generated error message + */ + @Override + public String apply(ValueContext context); +} diff --git a/server/src/main/java/com/vaadin/data/Validator.java b/server/src/main/java/com/vaadin/data/Validator.java index 6f1d5827ec..45a88bc7f3 100644 --- a/server/src/main/java/com/vaadin/data/Validator.java +++ b/server/src/main/java/com/vaadin/data/Validator.java @@ -101,15 +101,37 @@ public interface Validator String errorMessage) { Objects.requireNonNull(guard, "guard cannot be null"); Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); + return from(guard, ctx -> errorMessage); + } + + /** + * Builds a validator out of a conditional function and an error message + * provider. If the function returns true, the validator returns + * {@code Result.ok()}; if it returns false or throws an exception, + * {@code Result.error()} is returned with the message from the provider. + * + * @param + * the value type + * @param guard + * the function used to validate, not null + * @param errorMessageProvider + * the provider to generate error messages, not null + * @return the new validator using the function + */ + public static Validator from(SerializablePredicate guard, + ErrorMessageProvider errorMessageProvider) { + Objects.requireNonNull(guard, "guard cannot be null"); + Objects.requireNonNull(errorMessageProvider, + "errorMessageProvider cannot be null"); return (value, context) -> { try { if (guard.test(value)) { return Result.ok(value); } else { - return Result.error(errorMessage); + return Result.error(errorMessageProvider.apply(context)); } } catch (Exception e) { - return Result.error(errorMessage); + return Result.error(errorMessageProvider.apply(context)); } }; } diff --git a/server/src/test/java/com/vaadin/data/ValidatorTest.java b/server/src/test/java/com/vaadin/data/ValidatorTest.java index ead9991b4c..fa533c5010 100644 --- a/server/src/test/java/com/vaadin/data/ValidatorTest.java +++ b/server/src/test/java/com/vaadin/data/ValidatorTest.java @@ -15,18 +15,20 @@ */ package com.vaadin.data; +import java.util.Locale; import java.util.Objects; import org.junit.Assert; import org.junit.Test; import com.vaadin.data.util.converter.ValueContext; +import com.vaadin.data.validator.ValidatorTestBase; /** * @author Vaadin Ltd * */ -public class ValidatorTest { +public class ValidatorTest extends ValidatorTestBase { @Test public void alwaysPass() { @@ -47,4 +49,30 @@ public class ValidatorTest { result = validator.apply("", new ValueContext()); Assert.assertFalse(result.isError()); } + + @Test + public void withValidator_customErrorMessageProvider() { + String finnishError = "Käyttäjän tulee olla täysi-ikäinen"; + String englishError = "The user must be an adult"; + String notTranslatableError = "NOT TRANSLATABLE"; + + Validator ageValidator = Validator.from(age -> age >= 18, + ctx -> { + Locale locale = ctx.getLocale().orElse(Locale.ENGLISH); + + if (locale.getLanguage().equals("fi")) { + return finnishError; + } else if (locale.getLanguage().equals("en")) { + return englishError; + } + return notTranslatableError; + }); + + setLocale(Locale.ENGLISH); + assertFails(17, englishError, ageValidator); + setLocale(new Locale("fi", "FI")); + assertFails(17, finnishError, ageValidator); + setLocale(Locale.GERMAN); + assertFails(17, notTranslatableError, ageValidator); + } } diff --git a/server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java b/server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java index 3d6cd2afe1..5a7198f33c 100644 --- a/server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java +++ b/server/src/test/java/com/vaadin/data/validator/ValidatorTestBase.java @@ -1,12 +1,23 @@ package com.vaadin.data.validator; +import java.util.Locale; + import org.junit.Assert; +import org.junit.Before; import com.vaadin.data.Validator; import com.vaadin.data.util.converter.ValueContext; +import com.vaadin.ui.Label; public class ValidatorTestBase { + private Label localeContext; + + @Before + public void setUp() { + localeContext = new Label(); + } + protected void assertPasses(T value, Validator v) { v.apply(value, new ValueContext()) .handle(val -> Assert.assertEquals(value, val), err -> Assert @@ -15,7 +26,7 @@ public class ValidatorTestBase { protected void assertFails(T value, String errorMessage, Validator v) { - v.apply(value, new ValueContext()).handle( + v.apply(value, new ValueContext(localeContext)).handle( val -> Assert.fail(value + " should fail " + v), err -> Assert.assertEquals(errorMessage, err)); } @@ -23,4 +34,8 @@ public class ValidatorTestBase { protected void assertFails(T value, AbstractValidator v) { assertFails(value, v.getMessage(value), v); } -} + + protected void setLocale(Locale locale) { + localeContext.setLocale(locale); + } +} \ No newline at end of file -- cgit v1.2.3