- Revision
- 727
- Author
- mauro
- Date
- 2008-06-17 11:23:43 -0500 (Tue, 17 Jun 2008)
Log Message
WAFFLE-85: Addd NumberListValueConverter. Refactored ListValueConverter to only convert List<String> objects.
Modified Paths
- trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/ListValueConverter.java
- trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/ListValueConverterTest.java
Added Paths
Diff
Modified: trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/ListValueConverter.java (726 => 727)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/ListValueConverter.java 2008-06-17 15:31:18 UTC (rev 726) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/ListValueConverter.java 2008-06-17 16:23:43 UTC (rev 727) @@ -5,8 +5,6 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.text.NumberFormat; -import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -16,24 +14,15 @@ /** * <p> * <code>ValueConverter</code> that converts a CSV value to a List. A <code>null</code> or empty value (once - * trimmed) will be returned as an empty list (behaviour which can be overridden via the - * [EMAIL PROTECTED] #convertMissingValue} method). The message keys and default values used are: + * trimmed) will be returned as an empty list (behaviour which can be overridden via the [EMAIL PROTECTED] #convertMissingValue} + * method). The message keys and default values used are: * <ul> * <li>"bind.error.list" ([EMAIL PROTECTED] #BIND_ERROR_LIST_KEY}): list is <code>null</code> or empty (message defaults to * [EMAIL PROTECTED] #DEFAULT_LIST_MESSAGE})</li> - * <li>"list.number.pattern" ([EMAIL PROTECTED] #NUMBER_PATTERN_KEY}): pattern to use to identify the list as parseable numbers - * (defaults to [EMAIL PROTECTED] #DEFAULT_NUMBER_PATTERN})</li> * </ul> * The patterns are also optionally injectable via <code>Properties</code> in the constructor and take precedence over * the ones configured in the messages resources. * </p> - * <p> - * NOTE: the converter will first check if the values match the configured number regex pattern and only if it does will - * it attempt to parse them (using the <code>NumberFormat</code> instance provided, which defaults to - * <code>NumberFormat.getInstance()</code>) and if not successful returns the string values. The reason for the - * presence of the preliminary number pattern matching is to disable the attempt of number parsing altogether for some - * string values that may start with number and may be erronously parsed as numbers. - * </p> * * @author Mauro Talevi */ @@ -41,29 +30,25 @@ public static final String BIND_ERROR_LIST_KEY = "bind.error.list"; public static final String DEFAULT_LIST_MESSAGE = "Invalid list value for field {0}"; - public static final String NUMBER_PATTERN_KEY = "list.number.pattern"; - public static final String DEFAULT_NUMBER_PATTERN = "[0-9.-]*"; - + private static final String COMMA = ","; - private NumberFormat numberFormat; private Properties patterns; public ListValueConverter(MessageResources messageResources) { - this(messageResources, NumberFormat.getInstance(), new Properties()); + this(messageResources, new Properties()); } - public ListValueConverter(MessageResources messageResources, NumberFormat numberFormat, Properties patterns) { + public ListValueConverter(MessageResources messageResources, Properties patterns) { super(messageResources); - this.numberFormat = numberFormat; this.patterns = patterns; } public boolean accept(Type type) { - if ( type instanceof Class ){ - return List.class.isAssignableFrom((Class<?>)type); - } else if ( type instanceof ParameterizedType ){ - Type rawType = ((ParameterizedType)type).getRawType(); - return List.class.isAssignableFrom((Class<?>)rawType); + if (type instanceof Class) { + return List.class.isAssignableFrom((Class<?>) type); + } else if (type instanceof ParameterizedType) { + Type rawType = ((ParameterizedType) type).getRawType(); + return List.class.isAssignableFrom((Class<?>) rawType); } return false; } @@ -76,22 +61,14 @@ return convertMissingValue(BIND_ERROR_LIST_KEY, DEFAULT_LIST_MESSAGE, fieldName); } - List<String> values = listValues(value); - if (areNumbers(values)) { - try { - return toNumbers(values); - } catch (ParseException e) { - // failed to parse as numbers, return string values - } - } - return values; + return listValues(value); } private List<String> listValues(String value) { - String[] values = value.split(COMMA); + String[] values = value.split(COMMA); List<String> list = new ArrayList<String>(); - for ( String current : values ){ - if ( current.trim().length() > 0 ){ + for (String current : values) { + if (current.trim().length() > 0) { list.add(current); } } @@ -110,26 +87,5 @@ protected Object convertMissingValue(String key, String defaultMessage, Object... parameters) { return new ArrayList(); } - - protected boolean areNumbers(List<String> values) { - if (values.size() == 0) { - return false; // return empty list - } - String numberPattern = patternFor(patterns, NUMBER_PATTERN_KEY, DEFAULT_NUMBER_PATTERN); - for (String value : values) { - if (!matches(value, numberPattern)) { - return false; - } - } - return true; - } - - protected List<Number> toNumbers(List<String> values) throws ParseException { - List<Number> numbers = new ArrayList<Number>(); - for (String value : values) { - numbers.add(numberFormat.parse(value)); - } - return numbers; - } }
Added: trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/NumberListValueConverter.java (0 => 727)
--- trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/NumberListValueConverter.java (rev 0) +++ trunk/waffle-core/src/main/java/org/codehaus/waffle/bind/converters/NumberListValueConverter.java 2008-06-17 16:23:43 UTC (rev 727) @@ -0,0 +1,121 @@ +/* + * Copyright (c) terms as published in http://waffle.codehaus.org/license.html + */ +package org.codehaus.waffle.bind.converters; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.codehaus.waffle.i18n.MessageResources; + +/** + * <p> + * <code>ValueConverter</code> that converts a CSV value to a List. A <code>null</code> or empty value (once + * trimmed) will be returned as an empty list (behaviour which can be overridden via the [EMAIL PROTECTED] #convertMissingValue} + * method). The message keys and default values used are: + * <ul> + * <li>"bind.error.list" ([EMAIL PROTECTED] #BIND_ERROR_LIST_KEY}): list is <code>null</code> or empty (message defaults to + * [EMAIL PROTECTED] #DEFAULT_LIST_MESSAGE})</li> + * </ul> + * The patterns are also optionally injectable via <code>Properties</code> in the constructor and take precedence over + * the ones configured in the messages resources. + * </p> + * <p> + * NOTE: the converter will first check if the values match the configured number regex pattern and only if it does will + * it attempt to parse them (using the <code>NumberFormat</code> instance provided, which defaults to + * <code>NumberFormat.getInstance()</code>) and if not successful returns the string values. The reason for the + * presence of the preliminary number pattern matching is to disable the attempt of number parsing altogether for some + * string values that may start with number and may be erronously parsed as numbers. + * </p> + * + * @author Mauro Talevi + */ +public class NumberListValueConverter extends AbstractValueConverter { + + public static final String BIND_ERROR_LIST_KEY = "bind.error.list"; + public static final String DEFAULT_LIST_MESSAGE = "Invalid list value for field {0}"; + + private static final String COMMA = ","; + private NumberFormat numberFormat; + private Properties patterns; + + public NumberListValueConverter(MessageResources messageResources) { + this(messageResources, NumberFormat.getInstance(), new Properties()); + } + + public NumberListValueConverter(MessageResources messageResources, NumberFormat numberFormat, Properties patterns) { + super(messageResources); + this.numberFormat = numberFormat; + this.patterns = patterns; + } + + /** + * Accepts types of raw type List and argument type Number + */ + public boolean accept(Type type) { + if (type instanceof Class) { + return List.class.isAssignableFrom((Class<?>) type); + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = parameterizedType.getRawType(); + Type argumentType = parameterizedType.getActualTypeArguments()[0]; + return List.class.isAssignableFrom((Class<?>) rawType) && Number.class.isAssignableFrom((Class<?>)argumentType); + } + return false; + } + + @SuppressWarnings( { "unchecked" }) + public Object convertValue(String propertyName, String value, Type toType) { + + if (missingValue(value)) { + String fieldName = messageFor(propertyName, propertyName); + return convertMissingValue(BIND_ERROR_LIST_KEY, DEFAULT_LIST_MESSAGE, fieldName); + } + + List<String> values = listValues(value); + try { + return toNumbers(values); + } catch (ParseException e) { + // failed to parse as numbers, return string values + } + return values; + } + + private List<String> listValues(String value) { + String[] values = value.split(COMMA); + List<String> list = new ArrayList<String>(); + for (String current : values) { + if (current.trim().length() > 0) { + list.add(current); + } + } + return list; + } + + public Properties getPatterns() { + return patterns; + } + + public void changePatterns(Properties patterns) { + this.patterns = patterns; + } + + @SuppressWarnings("unchecked") + protected Object convertMissingValue(String key, String defaultMessage, Object... parameters) { + return new ArrayList(); + } + + protected List<Number> toNumbers(List<String> values) throws ParseException { + List<Number> numbers = new ArrayList<Number>(); + for (String value : values) { + numbers.add(numberFormat.parse(value)); + } + return numbers; + } + +}
Modified: trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/ListValueConverterTest.java (726 => 727)
--- trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/ListValueConverterTest.java 2008-06-17 15:31:18 UTC (rev 726) +++ trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/ListValueConverterTest.java 2008-06-17 16:23:43 UTC (rev 727) @@ -48,22 +48,24 @@ } @Test - public void canConvert() throws OgnlException { + public void canConvertLists() throws OgnlException { DefaultMessageResources resources = new DefaultMessageResources(configuration); ListValueConverter converter = new ListValueConverter(resources); - assertCanConvertValueToList(converter, INTEGERS, "-1,-2,-3", Integer.class); - assertCanConvertValueToList(converter, LONGS, "1000,2000,3000", Long.class); - assertCanConvertValueToList(converter, DOUBLES, "0.1,0.2,0.3", Double.class); - assertCanConvertValueToList(converter, FLOATS, "0.1,0.2,0.3", Float.class); - assertCanConvertValueToList(converter, STRINGS, "one,two,three", String.class); - assertCanConvertValueToList(converter, STRINGS, ",one,two,three", String.class); - assertCanConvertValueToList(converter, STRINGS, "one,,two,three", String.class); - assertCanConvertValueToList(converter, MIXED_STRINGS, "0#.A,1#.B", String.class); + //Note: no conversion is done from String to Numbers and the assertion is done on the string representation + assertCanConvertValueToList(converter, INTEGERS, "-1,-2,-3"); + assertCanConvertValueToList(converter, LONGS, "1000,2000,3000"); + assertCanConvertValueToList(converter, DOUBLES, "0.1,0.2,0.3"); + assertCanConvertValueToList(converter, FLOATS, "0.1,0.2,0.3"); + assertCanConvertValueToList(converter, STRINGS, "one,two,three"); + assertCanConvertValueToList(converter, STRINGS, ",one,two,three"); + assertCanConvertValueToList(converter, STRINGS, "one,,two,three"); + assertCanConvertValueToList(converter, MIXED_STRINGS, "0#.A,1#.B"); } - private void assertCanConvertValueToList(ListValueConverter converter, List<?> list, String value, Class<?> type) { - assertEquals(list.toString(), converter.convertValue("property-name", value, List.class).toString()); - assertTrue(list.get(0).getClass().isAssignableFrom(type)); + @SuppressWarnings("unchecked") + private void assertCanConvertValueToList(ListValueConverter converter, List<?> expected, String value) { + List<String> actual = (List<String>) converter.convertValue("property-name", value, List.class); + assertEquals(expected.toString(), actual.toString()); } @Test
Added: trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/NumberListValueConverterTest.java (0 => 727)
--- trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/NumberListValueConverterTest.java (rev 0) +++ trunk/waffle-core/src/test/java/org/codehaus/waffle/bind/converters/NumberListValueConverterTest.java 2008-06-17 16:23:43 UTC (rev 727) @@ -0,0 +1,151 @@ +package org.codehaus.waffle.bind.converters; + +import static java.text.MessageFormat.format; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.MethodDescriptor; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Locale; + +import ognl.OgnlException; + +import org.codehaus.waffle.bind.BindException; +import org.codehaus.waffle.i18n.DefaultMessageResources; +import org.codehaus.waffle.i18n.MessageResourcesConfiguration; +import org.junit.Test; + +/** + * + * @author Mauro Talevi + */ +public class NumberListValueConverterTest { + + private static final List<Integer> INTEGERS = asList(-1,-2,-3); + private static final List<Long> LONGS = asList(1000L,2000L,3000L); + private static final List<Double> DOUBLES = asList(0.1d,0.2d,0.3d); + private static final List<Float> FLOATS = asList(0.1f,0.2f,0.3f); + private static final List<String> STRINGS = asList("one","two","three"); + + private MessageResourcesConfiguration configuration = new MessageResourcesConfiguration(){ + + public Locale getDefaultLocale() { + return Locale.UK; + } + + public String getResourceBundleName() { + return "FakeResourceBundle"; + } + + }; + + private Type listType(String methodName) throws IntrospectionException { + BeanInfo beanInfo = Introspector.getBeanInfo(CustomType.class); + for (MethodDescriptor md : beanInfo.getMethodDescriptors()) { + if (md.getMethod().getName().equals(methodName)) { + return md.getMethod().getGenericParameterTypes()[0]; + } + } + return null; + } + + private static interface CustomType { + void listOfStrings(List<String> list); + void listOfIntegers(List<Integer> list); + void listOfLongs(List<Integer> list); + void listOfDoubles(List<Integer> list); + void listOfFloats(List<Integer> list); + }; + + @Test + public void canAccept() throws IntrospectionException { + NumberListValueConverter converter = new NumberListValueConverter(new DefaultMessageResources()); + assertTrue(converter.accept(listType("listOfIntegers"))); + assertTrue(converter.accept(listType("listOfLongs"))); + assertTrue(converter.accept(listType("listOfDoubles"))); + assertTrue(converter.accept(listType("listOfFloats"))); + assertFalse(converter.accept(listType("listOfStrings"))); + } + + @Test + public void canConvertListsOfNumbers() throws OgnlException, IntrospectionException { + DefaultMessageResources resources = new DefaultMessageResources(configuration); + NumberListValueConverter converter = new NumberListValueConverter(resources); + assertCanConvertValueToList(converter, INTEGERS, "-1,-2,-3", Long.class, "listOfIntegers"); + assertCanConvertValueToList(converter, LONGS, "1000,2000,3000", Long.class, "listOfLongs"); + assertCanConvertValueToList(converter, DOUBLES, "0.1,0.2,0.3", Double.class, "listOfDoubles"); + assertCanConvertValueToList(converter, FLOATS, "0.1,0.2,0.3", Double.class, "listOfFloats"); + } + + @Test //TODO decide if this behaviour is appropriate or if a bind exception should be thrown + public void canReturnListOfStringsIfParsingFails() throws OgnlException, IntrospectionException { + DefaultMessageResources resources = new DefaultMessageResources(configuration); + NumberListValueConverter converter = new NumberListValueConverter(resources); + assertCanConvertValueToList(converter, STRINGS, "one,two,three", String.class, "listOfStrings"); + } + + @SuppressWarnings("unchecked") + private void assertCanConvertValueToList(NumberListValueConverter converter, List<?> expected, String value, Class<?> expectedType, String methodName) throws IntrospectionException { + List<?> actual = (List<?>) converter.convertValue("property-name", value, listType(methodName)); + assertEquals(expected.toString(), actual.toString()); + assertTrue(expectedType.isAssignableFrom(actual.get(0).getClass())); + } + + @Test + public void canHandleMissingValues() { + NumberListValueConverter converter = new NumberListValueConverter(new DefaultMessageResources()); + assertEmptyList(converter, null); + assertEmptyList(converter, ""); + assertEmptyList(converter, " "); + } + + private void assertEmptyList(NumberListValueConverter converter, String value) { + List<?> list = (List<?>) converter.convertValue("property-name", value, List.class); + assertNotNull(list); + assertTrue(list.isEmpty()); + } + + @Test + public void canFailConversionWithCustomErrorMessages() { + DefaultMessageResources resources = new DefaultMessageResources(configuration); + NumberListValueConverter converter = new NumberListValueConverter(resources){ + + @Override + protected Object convertMissingValue(String key, String defaultMessage, Object... parameters) { + throw newBindException(key, defaultMessage, parameters); + } + }; + try { + converter.convertValue("property-name", null, List.class); + fail("Expected BindException"); + } catch ( BindException e) { + assertEquals(format(resources.getMessage(NumberListValueConverter.BIND_ERROR_LIST_KEY), "property-name"), e.getMessage()); + } + } + + @Test + public void canFailConversionWithDefaultErrorMessages() { + NumberListValueConverter converter = new NumberListValueConverter(new DefaultMessageResources()){ + @Override + protected Object convertMissingValue(String key, String defaultMessage, Object... parameters) { + throw newBindException(key, defaultMessage, parameters); + } + }; + try { + converter.convertValue("property-name", null, List.class); + fail("Expected BindException"); + } catch ( BindException e) { + assertEquals(format(NumberListValueConverter.DEFAULT_LIST_MESSAGE, "property-name"), e.getMessage()); + } + } + +} +
To unsubscribe from this list please visit:
