diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java index 78605e35bfc..0f01c3cffd9 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java @@ -29,6 +29,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; import org.springframework.context.MessageSource; import org.springframework.context.expression.MapAccessor; import org.springframework.context.i18n.LocaleContextHolder; @@ -350,7 +352,7 @@ public class GenericBinder implements Binder { public String format(Object selectableValue) { Formatter formatter = getFormatter(); - Class formattedType = getFormattedObjectType(formatter); + Class formattedType = getFormattedObjectType(formatter.getClass()); selectableValue = typeConverter.convert(selectableValue, formattedType); return formatter.format(selectableValue, LocaleContextHolder.getLocale()); } @@ -404,7 +406,8 @@ public class GenericBinder implements Binder { private BindingResult setStringValue(String formatted) { Object parsed; try { - parsed = getFormatter().parse(formatted, LocaleContextHolder.getLocale()); + Formatter formatter = getFormatter(); + parsed = formatter.parse(formatted, LocaleContextHolder.getLocale()); } catch (ParseException e) { return new InvalidFormat(property.getExpressionString(), formatted, e); } @@ -413,7 +416,7 @@ public class GenericBinder implements Binder { private BindingResult setStringValues(String[] formatted) { Formatter formatter = getFormatter(); - Class parsedType = getFormattedObjectType(formatter); + Class parsedType = getFormattedObjectType(formatter.getClass()); if (parsedType == null) { parsedType = String.class; } @@ -466,36 +469,38 @@ public class GenericBinder implements Binder { return new EvaluationError(property.getExpressionString(), userValue, e); } } - - private Class getFormattedObjectType(Formatter formatter) { + + private Class getFormattedObjectType(Class formatterClass) { // TODO consider caching this info - Class classToIntrospect = formatter.getClass(); + Class classToIntrospect = formatterClass; while (classToIntrospect != null) { - Type[] genericInterfaces = classToIntrospect.getGenericInterfaces(); - for (Type genericInterface : genericInterfaces) { - if (genericInterface instanceof ParameterizedType) { - ParameterizedType pInterface = (ParameterizedType) genericInterface; - if (Formatter.class.isAssignableFrom((Class) pInterface.getRawType())) { - return getParameterClass(pInterface.getActualTypeArguments()[0], formatter.getClass()); + Type[] ifcs = classToIntrospect.getGenericInterfaces(); + for (Type ifc : ifcs) { + if (ifc instanceof ParameterizedType) { + ParameterizedType paramIfc = (ParameterizedType) ifc; + Type rawType = paramIfc.getRawType(); + if (Formatter.class.equals(rawType)) { + Type arg = paramIfc.getActualTypeArguments()[0]; + if (arg instanceof TypeVariable) { + arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, formatterClass); + } + if (arg instanceof Class) { + return (Class) arg; + } + } + else if (ApplicationListener.class.isAssignableFrom((Class) rawType)) { + return getFormattedObjectType((Class) rawType); } } + else if (ApplicationListener.class.isAssignableFrom((Class) ifc)) { + return getFormattedObjectType((Class) ifc); + } } classToIntrospect = classToIntrospect.getSuperclass(); } return null; } - private Class getParameterClass(Type parameterType, Class converterClass) { - if (parameterType instanceof TypeVariable) { - parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); - } - if (parameterType instanceof Class) { - return (Class) parameterType; - } - throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType - + "] on Formatter [" + converterClass.getName() + "]"); - } - } static class DefaultFormatter implements Formatter { @@ -736,6 +741,8 @@ public class GenericBinder implements Binder { Throwable cause = accessException.getCause(); if (cause instanceof SpelEvaluationException && ((SpelEvaluationException) cause).getMessageCode() == SpelMessage.TYPE_CONVERSION_ERROR) { + // TODO this could be a ConverterExecutorNotFoundException if no suitable converter was found + cause.getCause().printStackTrace(); ConversionFailedException failure = (ConversionFailedException) cause.getCause(); MessageBuilder builder = new MessageBuilder(messageSource); builder.code("conversionFailed"); diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java index 3b7c66e2b55..f67bacba03b 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.text.ParseException; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; @@ -17,6 +18,7 @@ import junit.framework.Assert; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.ui.binding.Binding; @@ -253,6 +255,49 @@ public class GenericBinderTests { assertEquals("35452", bean.addresses.get(0).zip); } + @Test + public void bindToListSingleString() { + binder.addBinding("addresses").formatWith(new AddressListFormatter()); + Map values = new LinkedHashMap(); + values.put("addresses", "4655 Macy Lane:Melbourne:FL:35452,1234 Rostock Circle:Palm Bay:FL:32901,1977 Bel Aire Estates:Coker:AL:12345"); + BindingResults results = binder.bind(values); + System.out.println(results); + Assert.assertEquals(3, bean.addresses.size()); + assertEquals("4655 Macy Lane", bean.addresses.get(0).street); + assertEquals("Melbourne", bean.addresses.get(0).city); + assertEquals("FL", bean.addresses.get(0).state); + assertEquals("35452", bean.addresses.get(0).zip); + assertEquals("1234 Rostock Circle", bean.addresses.get(1).street); + assertEquals("Palm Bay", bean.addresses.get(1).city); + assertEquals("FL", bean.addresses.get(1).state); + assertEquals("32901", bean.addresses.get(1).zip); + assertEquals("1977 Bel Aire Estates", bean.addresses.get(2).street); + assertEquals("Coker", bean.addresses.get(2).city); + assertEquals("AL", bean.addresses.get(2).state); + assertEquals("12345", bean.addresses.get(2).zip); + } + + @Test + public void getCollectionAsSingleValue() { + binder.addBinding("addresses").formatWith(new AddressListFormatter()); + Address address1 = new Address(); + address1.setStreet("s1"); + address1.setCity("c1"); + address1.setState("st1"); + address1.setZip("z1"); + Address address2 = new Address(); + address2.setStreet("s2"); + address2.setCity("c2"); + address2.setState("st2"); + address2.setZip("z2"); + List
addresses = new ArrayList
(2); + addresses.add(address1); + addresses.add(address2); + bean.addresses = addresses; + String value = binder.getBinding("addresses").getValue(); + assertEquals("s1:c1:st1:z1,s2:c2:st2:z2,", value); + } + @Test public void bindToListHandleNullValueInNestedPath() { binder.addBinding("addresses.street"); @@ -403,6 +448,28 @@ public class GenericBinderTests { } + public static class AddressListFormatter implements Formatter> { + + public String format(List
addresses, Locale locale) { + StringBuilder builder = new StringBuilder(); + for (Address address : addresses) { + builder.append(new AddressFormatter().format(address, locale)); + builder.append(","); + } + return builder.toString(); + } + + public List
parse(String formatted, Locale locale) throws ParseException { + String[] fields = formatted.split(","); + List
addresses = new ArrayList
(fields.length); + for (String field : fields) { + addresses.add(new AddressFormatter().parse(field, locale)); + } + return addresses; + } + + } + @Formatted(AddressFormatter.class) public static class Address { private String street;