From a437fdfc7d687f204544be362e9fce509cd29265 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Tue, 9 Jun 2009 15:21:34 +0000 Subject: [PATCH] binding result tracking --- .../springframework/ui/binding/Binder.java | 281 +++++++++++++----- .../springframework/ui/binding/Binding.java | 5 +- .../ui/binding/BindingResult.java | 52 ++++ .../springframework/ui/binding/UserValue.java | 82 +++++ .../ui/binding/BinderTests.java | 126 ++++---- 5 files changed, 419 insertions(+), 127 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/binding/UserValue.java diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binder.java index 6b4cdf3cd23..54a18c99bd3 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binder.java @@ -21,8 +21,10 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.text.ParseException; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -72,7 +74,7 @@ public class Binder { private boolean strict = false; private static Formatter defaultFormatter = new Formatter() { - + public String format(Object object, Locale locale) { if (object == null) { return ""; @@ -81,16 +83,15 @@ public class Binder { } } - public Object parse(String formatted, Locale locale) - throws ParseException { + public Object parse(String formatted, Locale locale) throws ParseException { if (formatted == "") { return null; } else { return formatted; } - } + } }; - + /** * Creates a new binder for the model object. * @param model the model object containing properties this binder will bind to @@ -98,13 +99,12 @@ public class Binder { public Binder(T model) { this.model = model; bindings = new HashMap(); - int parserConfig = - SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull | - SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize; + int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull + | SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize; expressionParser = new SpelExpressionParser(parserConfig); typeConverter = new DefaultTypeConverter(); } - + /** * Configures if this binder is strict; a strict binder requires all bindings to be registered explicitly using {@link #add(BindingConfiguration)}. * An optimistic binder will implicitly create bindings as required to support {@link #bind(Map)} operations. @@ -140,10 +140,10 @@ public class Binder { if (propertyType.isAnnotation()) { annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter)); } else { - typeFormatters.put(propertyType, formatter); + typeFormatters.put(propertyType, formatter); } } - + /** * Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation. * @param factory the annotation formatter factory @@ -176,22 +176,22 @@ public class Binder { /** * Bind values in the map to the properties of the model object. + * TODO return BindingResults with getSuccesses()/getErrors()/etc? * @param propertyValues the property values map */ - public void bind(Map propertyValues) { - for (Map.Entry entry : propertyValues - .entrySet()) { - Binding binding = getBinding(entry.getKey()); - Object value = entry.getValue(); - if (value instanceof String) { - binding.setValue((String)entry.getValue()); - } - else if (value instanceof String[]) { - binding.setValues((String[])value); + public List bind(List userValues) { + List results = new ArrayList(userValues.size()); + for (UserValue value : userValues) { + Binding binding = getBinding(value.getProperty()); + if (value.isString()) { + results.add(binding.setValue((String) value.getValue())); + } else if (value.isStringArray()) { + results.add(binding.setValues((String[]) value.getValue())); } else { throw new IllegalArgumentException("Illegal argument " + value); } } + return results; } class BindingImpl implements Binding { @@ -200,34 +200,60 @@ public class Binder { private Formatter formatter; - public BindingImpl(BindingConfiguration config) - throws org.springframework.expression.ParseException { + public BindingImpl(BindingConfiguration config) throws org.springframework.expression.ParseException { property = expressionParser.parseExpression(config.getProperty()); formatter = config.getFormatter(); } public String getValue() { + Object value; try { - return format(property.getValue(createEvaluationContext())); + value = property.getValue(createEvaluationContext()); } catch (ExpressionException e) { - throw new IllegalArgumentException(e); + throw new IllegalStateException("Failed to get property expression value - this should not happen", e); } + return format(value); } - public void setValue(String formatted) { - setValue(parse(formatted, getFormatter())); + public BindingResult setValue(String formatted) { + Formatter formatter; + try { + formatter = getFormatter(); + } catch (EvaluationException e) { + // could occur the property was not found or is not readable + // TODO probably should not handle all EL failures, only type conversion & property not found? + return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e); + } + Object parsed; + try { + parsed = formatter.parse(formatted, LocaleContextHolder.getLocale()); + } catch (ParseException e) { + return new InvalidFormatResult(property.getExpressionString(), formatted, e); + } + return setValue(parsed, formatted); } public String format(Object selectableValue) { - Formatter formatter = getFormatter(); + Formatter formatter; + try { + formatter = getFormatter(); + } catch (EvaluationException e) { + throw new IllegalStateException("Failed to get property expression value type - this should not happen", e); + } Class formattedType = getFormattedObjectType(formatter); selectableValue = typeConverter.convert(selectableValue, formattedType); return formatter.format(selectableValue, LocaleContextHolder.getLocale()); } public boolean isCollection() { - TypeDescriptor type = TypeDescriptor.valueOf(getValueType()); - return type.isCollection() || type.isArray(); + Class type; + try { + type = getValueType(); + } catch (EvaluationException e) { + throw new IllegalArgumentException("Failed to get property expression value type - this should not happen", e); + } + TypeDescriptor typeDesc = TypeDescriptor.valueOf(type); + return typeDesc.isCollection() || typeDesc.isArray(); } public String[] getValues() { @@ -235,7 +261,7 @@ public class Binder { try { multiValue = property.getValue(createEvaluationContext()); } catch (EvaluationException e) { - throw new IllegalStateException(e); + throw new IllegalStateException("Failed to get property expression value - this should not happen", e); } if (multiValue == null) { return EMPTY_STRING_ARRAY; @@ -243,42 +269,47 @@ public class Binder { TypeDescriptor type = TypeDescriptor.valueOf(multiValue.getClass()); String[] formattedValues; if (type.isCollection()) { - Collection values = ((Collection)multiValue); + Collection values = ((Collection) multiValue); formattedValues = (String[]) Array.newInstance(String.class, values.size()); copy(values, formattedValues); } else if (type.isArray()) { formattedValues = (String[]) Array.newInstance(String.class, Array.getLength(multiValue)); - copy((Iterable) multiValue, formattedValues); + copy((Iterable) multiValue, formattedValues); } else { throw new IllegalStateException(); } return formattedValues; } - public void setValues(String[] formattedValues) { - Formatter formatter = getFormatter(); + public BindingResult setValues(String[] formatted) { + Formatter formatter; + try { + formatter = getFormatter(); + } catch (EvaluationException e) { + // could occur the property was not found or is not readable + // TODO probably should not handle all EL failures, only type conversion & property not found? + return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e); + } Class parsedType = getFormattedObjectType(formatter); if (parsedType == null) { parsedType = String.class; } - Object values = Array.newInstance(parsedType, formattedValues.length); - for (int i = 0; i < formattedValues.length; i++) { - Array.set(values, i, parse(formattedValues[i], formatter)); + Object parsed = Array.newInstance(parsedType, formatted.length); + for (int i = 0; i < formatted.length; i++) { + Object parsedValue; + try { + parsedValue = formatter.parse(formatted[i], LocaleContextHolder.getLocale()); + } catch (ParseException e) { + return new InvalidFormatResult(property.getExpressionString(), formatted, e); + } + Array.set(parsed, i, parsedValue); } - setValue(values); + return setValue(parsed, formatted); } // internal helpers - - private Object parse(String formatted, Formatter formatter) { - try { - return formatter.parse(formatted, LocaleContextHolder.getLocale()); - } catch (ParseException e) { - throw new IllegalArgumentException("Invalid format " + formatted, e); - } - } - private Formatter getFormatter() { + private Formatter getFormatter() throws EvaluationException { if (formatter != null) { return formatter; } else { @@ -299,21 +330,12 @@ public class Binder { } } - private Class getValueType() { - try { - return property.getValueType(createEvaluationContext()); - } catch (EvaluationException e) { - throw new IllegalStateException(e); - } + private Class getValueType() throws EvaluationException { + return property.getValueType(createEvaluationContext()); } - private Annotation[] getAnnotations() { - try { - return property.getValueTypeDescriptor( - createEvaluationContext()).getAnnotations(); - } catch (EvaluationException e) { - throw new IllegalStateException(e); - } + private Annotation[] getAnnotations() throws EvaluationException { + return property.getValueTypeDescriptor(createEvaluationContext()).getAnnotations(); } private void copy(Iterable values, String[] formattedValues) { @@ -323,15 +345,16 @@ public class Binder { i++; } } - - private void setValue(Object values) { + + private BindingResult setValue(Object parsed, Object formatted) { try { - property.setValue(createEvaluationContext(), values); - } catch (ExpressionException e) { - throw new IllegalArgumentException(e); + property.setValue(createEvaluationContext(), parsed); + return new SuccessResult(property.getExpressionString(), formatted); + } catch (EvaluationException e) { + return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e); } } - + } private EvaluationContext createEvaluationContext() { @@ -341,7 +364,7 @@ public class Binder { context.setTypeConverter(new StandardTypeConverter(typeConverter)); return context; } - + private Class getAnnotationType(AnnotationFormatterFactory factory) { Class classToIntrospect = factory.getClass(); while (classToIntrospect != null) { @@ -356,10 +379,11 @@ public class Binder { } classToIntrospect = classToIntrospect.getSuperclass(); } - throw new IllegalArgumentException("Unable to extract Annotation type A argument from AnnotationFormatterFactory [" - + factory.getClass().getName() + "]; does the factory parameterize the generic type?"); + throw new IllegalArgumentException( + "Unable to extract Annotation type A argument from AnnotationFormatterFactory [" + + factory.getClass().getName() + "]; does the factory parameterize the generic type?"); } - + private Class getFormattedObjectType(Formatter formatter) { // TODO consider caching this info Class classToIntrospect = formatter.getClass(); @@ -377,7 +401,7 @@ public class Binder { } return null; } - + private Class getParameterClass(Type parameterType, Class converterClass) { if (parameterType instanceof TypeVariable) { parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); @@ -392,7 +416,7 @@ public class Binder { static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory { private Formatter formatter; - + public SimpleAnnotationFormatterFactory(Formatter formatter) { this.formatter = formatter; } @@ -400,6 +424,115 @@ public class Binder { public Formatter getFormatter(Annotation annotation) { return formatter; } - + + } + + static class InvalidFormatResult implements BindingResult { + + private String property; + + private Object formatted; + + private ParseException e; + + public InvalidFormatResult(String property, Object formatted, ParseException e) { + this.property = property; + this.formatted = formatted; + this.e = e; + } + + public String getProperty() { + return property; + } + + public boolean isError() { + return true; + } + + public String getErrorCode() { + return "invalidFormat"; + } + + public Throwable getErrorCause() { + return e; + } + + public Object getUserValue() { + return formatted; + } + } + + static class ExpressionEvaluationErrorResult implements BindingResult { + + private String property; + + private Object formatted; + + private EvaluationException e; + + public ExpressionEvaluationErrorResult(String property, Object formatted, EvaluationException e) { + this.property = property; + this.formatted = formatted; + this.e = e; + } + + public String getProperty() { + return property; + } + + public boolean isError() { + return true; + } + + public String getErrorCode() { + if (e.getMessage().startsWith("EL1034E")) { + return "typeConversionFailure"; + } else if (e.getMessage().startsWith("EL1008E")) { + return "propertyNotFound"; + } else { + // TODO return more specific code based on underlying EvaluationException error code + return "couldNotSetValue"; + } + } + + public Throwable getErrorCause() { + return e; + } + + public Object getUserValue() { + return formatted; + } + } + + static class SuccessResult implements BindingResult { + + private String property; + + private Object formatted; + + public SuccessResult(String property, Object formatted) { + this.property = property; + this.formatted = formatted; + } + + public String getProperty() { + return property; + } + + public boolean isError() { + return false; + } + + public String getErrorCode() { + return null; + } + + public Throwable getErrorCause() { + return null; + } + + public Object getUserValue() { + return formatted; + } } } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java index fc5fec1dc1f..e3e73b4ce31 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Binding.java @@ -15,6 +15,7 @@ */ package org.springframework.ui.binding; + /** * A binding between a user interface element and a model property. * @author Keith Donald @@ -32,7 +33,7 @@ public interface Binding { * Sets the model property value a from user-entered value. * @param formatted the value entered by the user */ - void setValue(String formatted); + BindingResult setValue(String formatted); /** * Formats a candidate model property value for display in the user interface. @@ -59,6 +60,6 @@ public interface Binding { * When a collection binding, sets the model property values a from user-entered/selected values. * @param formattedValues the values entered by the user */ - void setValues(String[] formattedValues); + BindingResult setValues(String[] formatted); } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java new file mode 100644 index 00000000000..aefab4f1da2 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * 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 org.springframework.ui.binding; + +/** + * A data binding result. + * + * @author Keith Donald + */ +public interface BindingResult { + + /** + * The name of the model property associated with this binding result. + */ + String getProperty(); + + /** + * Indicates if this result is an error result. + */ + boolean isError(); + + /** + * If an error result, the error code; for example, "invalidFormat", "propertyNotFound", or "evaluationException". + */ + String getErrorCode(); + + /** + * If an error result, the cause of the error. + * @return the cause, or null if this is not an error + */ + Throwable getErrorCause(); + + /** + * The raw user-entered value for which binding was attempted. + * If not an error result, this value was successfully bound to the model. + */ + Object getUserValue(); + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/UserValue.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/UserValue.java new file mode 100644 index 00000000000..a185857075c --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/UserValue.java @@ -0,0 +1,82 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * 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 org.springframework.ui.binding; + +import java.util.ArrayList; +import java.util.List; + +/** + * Holds a user-entered value to bind to a model property. + * @author Keith Donald + * @see Binder#bind(List). + */ +public class UserValue { + + private String property; + + private Object value; + + /** + * Create a new user value + * @param property the property associated with the value + * @param value the actual user-entered value + */ + public UserValue(String property, Object value) { + this.property = property; + this.value = value; + } + + /** + * The property the user-entered value should bind to. + */ + public String getProperty() { + return property; + } + + /** + * The actual user-entered value. + */ + public Object getValue() { + return value; + } + + /** + * Is the user-entered value a String? + */ + public boolean isString() { + return value instanceof String; + } + + /** + * Is the user-entered value a String[]? + */ + public boolean isStringArray() { + return value instanceof String[]; + } + + /** + * Creates a new UserValue list with a single element. + * @param property the property + * @param value the actual user-entered value + * @return the singleton user value list + */ + public static List singleton(String property, Object value) { + List values = new ArrayList(1); + values.add(new UserValue(property, value)); + return values; + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/BinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/BinderTests.java index 9fe903d3196..cc2c5911e97 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/binding/BinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/BinderTests.java @@ -7,11 +7,10 @@ import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.text.ParseException; +import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import junit.framework.Assert; @@ -19,6 +18,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.expression.EvaluationException; import org.springframework.ui.format.date.DateFormatter; import org.springframework.ui.format.number.CurrencyAnnotationFormatterFactory; import org.springframework.ui.format.number.CurrencyFormat; @@ -40,54 +40,67 @@ public class BinderTests { @Test public void bindSingleValuesWithDefaultTypeConverterConversion() { Binder binder = new Binder(new TestBean()); - Map propertyValues = new HashMap(); - propertyValues.put("string", "test"); - propertyValues.put("integer", "3"); - propertyValues.put("foo", "BAR"); - binder.bind(propertyValues); + List values = new ArrayList(); + values.add(new UserValue("string", "test")); + values.add(new UserValue("integer", "3")); + values.add(new UserValue("foo", "BAR")); + List results = binder.bind(values); + assertEquals(3, results.size()); + + assertEquals("string", results.get(0).getProperty()); + assertFalse(results.get(0).isError()); + assertNull(results.get(0).getErrorCause()); + assertEquals("test", results.get(0).getUserValue()); + + assertEquals("integer", results.get(1).getProperty()); + assertFalse(results.get(1).isError()); + assertNull(results.get(1).getErrorCause()); + assertEquals("3", results.get(1).getUserValue()); + + assertEquals("foo", results.get(2).getProperty()); + assertFalse(results.get(2).isError()); + assertNull(results.get(2).getErrorCause()); + assertEquals("BAR", results.get(2).getUserValue()); + assertEquals("test", binder.getModel().getString()); assertEquals(3, binder.getModel().getInteger()); assertEquals(FooEnum.BAR, binder.getModel().getFoo()); } - // TODO should update error context, not throw exception - @Test(expected=IllegalArgumentException.class) + @Test public void bindSingleValuesWithDefaultTypeCoversionFailure() { Binder binder = new Binder(new TestBean()); - Map propertyValues = new HashMap(); - propertyValues.put("string", "test"); - propertyValues.put("integer", "bogus"); - propertyValues.put("foo", "bogus"); - binder.bind(propertyValues); + List values = new ArrayList(); + values.add(new UserValue("string", "test")); + // bad value + values.add(new UserValue("integer", "bogus")); + values.add(new UserValue("foo", "BAR")); + List results = binder.bind(values); + assertEquals(3, results.size()); + assertTrue(results.get(1).isError()); + assertEquals("typeConversionFailure", results.get(1).getErrorCode()); } @Test public void bindSingleValuePropertyFormatter() throws ParseException { Binder binder = new Binder(new TestBean()); binder.add(new BindingConfiguration("date", new DateFormatter())); - Map propertyValues = new HashMap(); - propertyValues.put("date", "2009-06-01"); - binder.bind(propertyValues); + binder.bind(UserValue.singleton("date", "2009-06-01")); assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), binder.getModel().getDate()); } - // TODO should update error context, not throw exception - @Test(expected=IllegalArgumentException.class) + @Test public void bindSingleValuePropertyFormatterParseException() { Binder binder = new Binder(new TestBean()); binder.add(new BindingConfiguration("date", new DateFormatter())); - Map propertyValues = new HashMap(); - propertyValues.put("date", "bogus"); - binder.bind(propertyValues); + binder.bind(UserValue.singleton("date", "bogus")); } @Test public void bindSingleValueWithFormatterRegistedByType() throws ParseException { Binder binder = new Binder(new TestBean()); binder.add(new DateFormatter(), Date.class); - Map propertyValues = new HashMap(); - propertyValues.put("date", "2009-06-01"); - binder.bind(propertyValues); + binder.bind(UserValue.singleton("date", "2009-06-01")); assertEquals(new DateFormatter().parse("2009-06-01", Locale.US), binder.getModel().getDate()); } @@ -95,9 +108,7 @@ public class BinderTests { public void bindSingleValueWithFormatterRegisteredByAnnotation() throws ParseException { Binder binder = new Binder(new TestBean()); binder.add(new CurrencyFormatter(), CurrencyFormat.class); - Map propertyValues = new HashMap(); - propertyValues.put("currency", "$23.56"); - binder.bind(propertyValues); + binder.bind(UserValue.singleton("currency", "$23.56")); assertEquals(new BigDecimal("23.56"), binder.getModel().getCurrency()); } @@ -105,20 +116,28 @@ public class BinderTests { public void bindSingleValueWithnAnnotationFormatterFactoryRegistered() throws ParseException { Binder binder = new Binder(new TestBean()); binder.add(new CurrencyAnnotationFormatterFactory()); - Map propertyValues = new HashMap(); - propertyValues.put("currency", "$23.56"); - binder.bind(propertyValues); + binder.bind(UserValue.singleton("currency", "$23.56")); assertEquals(new BigDecimal("23.56"), binder.getModel().getCurrency()); } + @Test + public void bindSingleValuePropertyNotFound() throws ParseException { + Binder binder = new Binder(new TestBean()); + List results = binder.bind(UserValue.singleton("bogus", "2009-06-01")); + assertEquals(1, results.size()); + assertTrue(results.get(0).isError()); + assertEquals("propertyNotFound", results.get(0).getErrorCode()); + } + @Test public void getBindingOptimistic() { Binder binder = new Binder(new TestBean()); Binding b = binder.getBinding("integer"); assertFalse(b.isCollection()); assertEquals("0", b.getValue()); - b.setValue("5"); + BindingResult result = b.setValue("5"); assertEquals("5", b.getValue()); + assertFalse(result.isError()); } @Test @@ -131,8 +150,9 @@ public class BinderTests { b = binder.getBinding("integer"); assertFalse(b.isCollection()); assertEquals("0", b.getValue()); - b.setValue("5"); + BindingResult result = b.setValue("5"); assertEquals("5", b.getValue()); + assertFalse(result.isError()); } @Test @@ -172,21 +192,24 @@ public class BinderTests { assertEquals("BAZ", values[1]); assertEquals("BOOP", values[2]); } - - @Test(expected=IllegalArgumentException.class) - public void getBindingMultiValuedTypeConversionError() { + + @Test + public void getBindingMultiValuedTypeConversionFailure() { Binder binder = new Binder(new TestBean()); Binding b = binder.getBinding("foos"); assertTrue(b.isCollection()); assertEquals(0, b.getValues().length); - b.setValues(new String[] { "BAR", "BOGUS", "BOOP" }); + BindingResult result = b.setValues(new String[] { "BAR", "BOGUS", "BOOP" }); + assertTrue(result.isError()); + assertTrue(result.getErrorCause() instanceof EvaluationException); + assertEquals("typeConversionFailure", result.getErrorCode()); } @Test public void bindHandleNullValueInNestedPath() { TestBean testbean = new TestBean(); Binder binder = new Binder(testbean); - Map propertyValues = new HashMap(); + List values = new ArrayList(); // EL configured with some options from SpelExpressionParserConfiguration: // (see where Binder creates the parser) @@ -195,27 +218,28 @@ public class BinderTests { // are new instances of the type of the list entry, they are not null. // not currently doing anything for maps or arrays - propertyValues.put("addresses[0].street", "4655 Macy Lane"); - propertyValues.put("addresses[0].city", "Melbourne"); - propertyValues.put("addresses[0].state", "FL"); - propertyValues.put("addresses[0].state", "35452"); + values.add(new UserValue("addresses[0].street", "4655 Macy Lane")); + values.add(new UserValue("addresses[0].city", "Melbourne")); + values.add(new UserValue("addresses[0].state", "FL")); + values.add(new UserValue("addresses[0].state", "35452")); // Auto adds new Address at 1 - propertyValues.put("addresses[1].street", "1234 Rostock Circle"); - propertyValues.put("addresses[1].city", "Palm Bay"); - propertyValues.put("addresses[1].state", "FL"); - propertyValues.put("addresses[1].state", "32901"); + values.add(new UserValue("addresses[1].street", "1234 Rostock Circle")); + values.add(new UserValue("addresses[1].city", "Palm Bay")); + values.add(new UserValue("addresses[1].state", "FL")); + values.add(new UserValue("addresses[1].state", "32901")); // Auto adds new Address at 5 (plus intermediates 2,3,4) - propertyValues.put("addresses[5].street", "1234 Rostock Circle"); - propertyValues.put("addresses[5].city", "Palm Bay"); - propertyValues.put("addresses[5].state", "FL"); - propertyValues.put("addresses[5].state", "32901"); + values.add(new UserValue("addresses[5].street", "1234 Rostock Circle")); + values.add(new UserValue("addresses[5].city", "Palm Bay")); + values.add(new UserValue("addresses[5].state", "FL")); + values.add(new UserValue("addresses[5].state", "32901")); - binder.bind(propertyValues); + List results = binder.bind(values); Assert.assertEquals(6,testbean.addresses.size()); Assert.assertEquals("Palm Bay",testbean.addresses.get(1).city); Assert.assertNotNull(testbean.addresses.get(2)); + assertEquals(12, results.size()); } @Test