@@ -21,6 +21,7 @@ import java.text.ParsePosition; | |||
import java.util.Locale; | |||
import com.vaadin.data.Converter; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -39,9 +40,27 @@ import com.vaadin.data.ValueContext; | |||
public abstract class AbstractStringToNumberConverter<T extends Number> | |||
implements Converter<String, T> { | |||
private final String errorMessage; | |||
private final ErrorMessageProvider errorMessageProvider; | |||
private T emptyValue; | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
protected AbstractStringToNumberConverter(T emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
this.emptyValue = emptyValue; | |||
this.errorMessageProvider = errorMessageProvider; | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message. | |||
@@ -54,8 +73,7 @@ public abstract class AbstractStringToNumberConverter<T extends Number> | |||
*/ | |||
protected AbstractStringToNumberConverter(T emptyValue, | |||
String errorMessage) { | |||
this.emptyValue = emptyValue; | |||
this.errorMessage = errorMessage; | |||
this(emptyValue, ctx -> errorMessage); | |||
} | |||
/** | |||
@@ -81,11 +99,12 @@ public abstract class AbstractStringToNumberConverter<T extends Number> | |||
* | |||
* @param value | |||
* The value to convert | |||
* @param locale | |||
* The locale to use for conversion | |||
* @param context | |||
* The value context for conversion | |||
* @return The converted value | |||
*/ | |||
protected Result<Number> convertToNumber(String value, Locale locale) { | |||
protected Result<Number> convertToNumber(String value, | |||
ValueContext context) { | |||
if (value == null) { | |||
return Result.ok(null); | |||
} | |||
@@ -96,9 +115,10 @@ public abstract class AbstractStringToNumberConverter<T extends Number> | |||
// Parse and detect errors. If the full string was not used, it is | |||
// an error. | |||
ParsePosition parsePosition = new ParsePosition(0); | |||
Number parsedValue = getFormat(locale).parse(value, parsePosition); | |||
Number parsedValue = getFormat(context.getLocale().orElse(null)) | |||
.parse(value, parsePosition); | |||
if (parsePosition.getIndex() != value.length()) { | |||
return Result.error(getErrorMessage()); | |||
return Result.error(getErrorMessage(context)); | |||
} | |||
if (parsedValue == null) { | |||
@@ -114,8 +134,8 @@ public abstract class AbstractStringToNumberConverter<T extends Number> | |||
* | |||
* @return the error message | |||
*/ | |||
protected String getErrorMessage() { | |||
return errorMessage; | |||
protected String getErrorMessage(ValueContext context) { | |||
return errorMessageProvider.apply(context); | |||
} | |||
@Override |
@@ -20,6 +20,7 @@ import java.text.DecimalFormat; | |||
import java.text.NumberFormat; | |||
import java.util.Locale; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -66,6 +67,37 @@ public class StringToBigDecimalConverter | |||
super(emptyValue, errorMessage); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToBigDecimalConverter( | |||
ErrorMessageProvider errorMessageProvider) { | |||
this(null, errorMessageProvider); | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToBigDecimalConverter(BigDecimal emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
super(emptyValue, errorMessageProvider); | |||
} | |||
@Override | |||
protected NumberFormat getFormat(Locale locale) { | |||
NumberFormat numberFormat = super.getFormat(locale); | |||
@@ -79,7 +111,7 @@ public class StringToBigDecimalConverter | |||
@Override | |||
public Result<BigDecimal> convertToModel(String value, | |||
ValueContext context) { | |||
return convertToNumber(value, context.getLocale().orElse(null)) | |||
return convertToNumber(value, context) | |||
.map(number -> (BigDecimal) number); | |||
} | |||
@@ -21,6 +21,7 @@ import java.text.DecimalFormat; | |||
import java.text.NumberFormat; | |||
import java.util.Locale; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -67,6 +68,37 @@ public class StringToBigIntegerConverter | |||
super(emptyValue, errorMessage); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToBigIntegerConverter( | |||
ErrorMessageProvider errorMessageProvider) { | |||
this(null, errorMessageProvider); | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToBigIntegerConverter(BigInteger emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
super(emptyValue, errorMessageProvider); | |||
} | |||
@Override | |||
protected NumberFormat getFormat(Locale locale) { | |||
NumberFormat numberFormat = super.getFormat(locale); | |||
@@ -80,17 +112,16 @@ public class StringToBigIntegerConverter | |||
@Override | |||
public Result<BigInteger> convertToModel(String value, | |||
ValueContext context) { | |||
return convertToNumber(value, context.getLocale().orElse(null)) | |||
.map(number -> { | |||
if (number == null) { | |||
return null; | |||
} | |||
// Empty value will be a BigInteger | |||
if (number instanceof BigInteger) { | |||
return (BigInteger) number; | |||
} | |||
return ((BigDecimal) number).toBigInteger(); | |||
}); | |||
return convertToNumber(value, context).map(number -> { | |||
if (number == null) { | |||
return null; | |||
} | |||
// Empty value will be a BigInteger | |||
if (number instanceof BigInteger) { | |||
return (BigInteger) number; | |||
} | |||
return ((BigDecimal) number).toBigInteger(); | |||
}); | |||
} | |||
} |
@@ -19,13 +19,15 @@ package com.vaadin.data.converter; | |||
import java.util.Locale; | |||
import com.vaadin.data.Converter; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
/** | |||
* A converter that converts from {@link String} to {@link Boolean} and back. | |||
* The String representation is given by {@link Boolean#toString()} or provided | |||
* in constructor {@link StringToBooleanConverter#StringToBooleanConverter(String, String, String)}. | |||
* in constructor | |||
* {@link StringToBooleanConverter#StringToBooleanConverter(String, String, String)}. | |||
* <p> | |||
* Leading and trailing white spaces are ignored when converting from a String. | |||
* </p> | |||
@@ -43,7 +45,7 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { | |||
private final String falseString; | |||
private String errorMessage; | |||
private ErrorMessageProvider errorMessageProvider; | |||
/** | |||
* Creates converter with default string representations - "true" and | |||
@@ -56,6 +58,20 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { | |||
this(errorMessage, Boolean.TRUE.toString(), Boolean.FALSE.toString()); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToBooleanConverter(ErrorMessageProvider errorMessageProvider) { | |||
this(Boolean.TRUE.toString(), Boolean.FALSE.toString(), | |||
errorMessageProvider); | |||
} | |||
/** | |||
* Creates converter with custom string representation. | |||
* | |||
@@ -68,7 +84,24 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { | |||
*/ | |||
public StringToBooleanConverter(String errorMessage, String trueString, | |||
String falseString) { | |||
this.errorMessage = errorMessage; | |||
this(trueString, falseString, ctx -> errorMessage); | |||
} | |||
/** | |||
* Creates converter with custom string representation. | |||
* | |||
* @param falseString | |||
* string representation for <code>false</code> | |||
* @param trueString | |||
* string representation for <code>true</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToBooleanConverter(String trueString, String falseString, | |||
ErrorMessageProvider errorMessageProvider) { | |||
this.errorMessageProvider = errorMessageProvider; | |||
this.trueString = trueString; | |||
this.falseString = falseString; | |||
} | |||
@@ -90,7 +123,7 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { | |||
} else if (value.isEmpty()) { | |||
return Result.ok(null); | |||
} else { | |||
return Result.error(errorMessage); | |||
return Result.error(errorMessageProvider.apply(context)); | |||
} | |||
} | |||
@@ -19,6 +19,7 @@ package com.vaadin.data.converter; | |||
import java.text.NumberFormat; | |||
import java.util.Locale; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -64,10 +65,39 @@ public class StringToDoubleConverter | |||
super(emptyValue, errorMessage); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToDoubleConverter(ErrorMessageProvider errorMessageProvider) { | |||
this(null, errorMessageProvider); | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToDoubleConverter(Double emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
super(emptyValue, errorMessageProvider); | |||
} | |||
@Override | |||
public Result<Double> convertToModel(String value, ValueContext context) { | |||
Result<Number> n = convertToNumber(value, | |||
context.getLocale().orElse(null)); | |||
Result<Number> n = convertToNumber(value, context); | |||
return n.map(number -> { | |||
if (number == null) { |
@@ -19,6 +19,7 @@ package com.vaadin.data.converter; | |||
import java.text.NumberFormat; | |||
import java.util.Locale; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -62,10 +63,39 @@ public class StringToFloatConverter | |||
super(emptyValue, errorMessage); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToFloatConverter(ErrorMessageProvider errorMessageProvider) { | |||
this(null, errorMessageProvider); | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToFloatConverter(Float emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
super(emptyValue, errorMessageProvider); | |||
} | |||
@Override | |||
public Result<Float> convertToModel(String value, ValueContext context) { | |||
Result<Number> n = convertToNumber(value, | |||
context.getLocale().orElse(null)); | |||
Result<Number> n = convertToNumber(value, context); | |||
return n.map(number -> { | |||
if (number == null) { |
@@ -19,6 +19,7 @@ package com.vaadin.data.converter; | |||
import java.text.NumberFormat; | |||
import java.util.Locale; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -61,6 +62,36 @@ public class StringToIntegerConverter | |||
super(emptyValue, errorMessage); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToIntegerConverter(ErrorMessageProvider errorMessageProvider) { | |||
this(null, errorMessageProvider); | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToIntegerConverter(Integer emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
super(emptyValue, errorMessageProvider); | |||
} | |||
/** | |||
* Returns the format used by | |||
* {@link #convertToPresentation(Object, ValueContext)} and | |||
@@ -80,8 +111,7 @@ public class StringToIntegerConverter | |||
@Override | |||
public Result<Integer> convertToModel(String value, ValueContext context) { | |||
Result<Number> n = convertToNumber(value, | |||
context.getLocale().orElse(null)); | |||
Result<Number> n = convertToNumber(value, context); | |||
return n.flatMap(number -> { | |||
if (number == null) { | |||
return Result.ok(null); | |||
@@ -94,7 +124,7 @@ public class StringToIntegerConverter | |||
// long and thus does not need to consider wrap-around. | |||
return Result.ok(intValue); | |||
} | |||
return Result.error(getErrorMessage()); | |||
return Result.error(getErrorMessage(context)); | |||
}); | |||
} | |||
@@ -19,6 +19,7 @@ package com.vaadin.data.converter; | |||
import java.text.NumberFormat; | |||
import java.util.Locale; | |||
import com.vaadin.data.ErrorMessageProvider; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValueContext; | |||
@@ -61,6 +62,36 @@ public class StringToLongConverter | |||
super(emptyValue, errorMessage); | |||
} | |||
/** | |||
* Creates a new converter instance with the given error message provider. | |||
* Empty strings are converted to <code>null</code>. | |||
* | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToLongConverter(ErrorMessageProvider errorMessageProvider) { | |||
this(null, errorMessageProvider); | |||
} | |||
/** | |||
* Creates a new converter instance with the given empty string value and | |||
* error message provider. | |||
* | |||
* @param emptyValue | |||
* the presentation value to return when converting an empty | |||
* string, may be <code>null</code> | |||
* @param errorMessageProvider | |||
* the error message provider to use if conversion fails | |||
* | |||
* @since | |||
*/ | |||
public StringToLongConverter(Long emptyValue, | |||
ErrorMessageProvider errorMessageProvider) { | |||
super(emptyValue, errorMessageProvider); | |||
} | |||
/** | |||
* Returns the format used by | |||
* {@link #convertToPresentation(Object, ValueContext)} and | |||
@@ -80,8 +111,7 @@ public class StringToLongConverter | |||
@Override | |||
public Result<Long> convertToModel(String value, ValueContext context) { | |||
Result<Number> n = convertToNumber(value, | |||
context.getLocale().orElse(null)); | |||
Result<Number> n = convertToNumber(value, context); | |||
return n.map(number -> { | |||
if (number == null) { | |||
return null; |
@@ -1102,4 +1102,27 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> { | |||
assertTrue("Binding should be readonly", binding.isReadOnly()); | |||
assertTrue("Name field should be readonly", nameField.isReadOnly()); | |||
} | |||
@Test | |||
public void conversionWithLocaleBasedErrorMessage() { | |||
String fiError = "VIRHE"; | |||
String otherError = "ERROR"; | |||
binder.forField(ageField).withConverter(new StringToIntegerConverter( | |||
context -> context.getLocale().map(Locale::getLanguage) | |||
.orElse("en").equals("fi") ? fiError : otherError)) | |||
.bind(Person::getAge, Person::setAge); | |||
binder.setBean(item); | |||
ageField.setValue("not a number"); | |||
assertEquals(otherError, | |||
ageField.getErrorMessage().getFormattedHtmlMessage()); | |||
ageField.setLocale(new Locale("fi")); | |||
// Re-validate to get the error message with correct locale | |||
binder.validate(); | |||
assertEquals(fiError, | |||
ageField.getErrorMessage().getFormattedHtmlMessage()); | |||
} | |||
} |