From 0c1c5fffbafda98f490217f677ef6e4855ac03d0 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Thu, 9 Jul 2009 05:18:15 +0000 Subject: [PATCH] collection property type formatters --- .../ui/binding/BindingFactory.java | 1 + .../binding/MissingSourceValuesException.java | 1 + .../ui/binding/NoSuchBindingException.java | 1 + .../ui/binding/support/FormatterRegistry.java | 8 +++ .../ui/binding/support/GenericBinder.java | 16 ++++- .../GenericCollectionPropertyType.java | 64 +++++++++++++++++++ .../support/GenericFormatterRegistry.java | 29 ++++++--- .../springframework/ui/format/Formatted.java | 2 + .../binding/support/GenericBinderTests.java | 6 +- 9 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericCollectionPropertyType.java diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingFactory.java index b29f8894d01..1d7cefecabf 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingFactory.java @@ -18,6 +18,7 @@ package org.springframework.ui.binding; /** * A factory for model property bindings. * @author Keith Donald + * @since 3.0 */ public interface BindingFactory { diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/MissingSourceValuesException.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/MissingSourceValuesException.java index 62012516be2..a31962f6c70 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/MissingSourceValuesException.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/MissingSourceValuesException.java @@ -22,6 +22,7 @@ import java.util.Map; * Exception thrown by a Binder when a required source value is missing unexpectedly from the sourceValues map. * Indicates a client configuration error. * @author Keith Donald + * @since 3.0 * @see Binder#bind(Map) */ public class MissingSourceValuesException extends RuntimeException { diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/NoSuchBindingException.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/NoSuchBindingException.java index b184781c623..f6f9b49b1ef 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/NoSuchBindingException.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/NoSuchBindingException.java @@ -3,6 +3,7 @@ package org.springframework.ui.binding; /** * Thrown by a BindingFactory when no binding to a property exists. * @author Keith Donald + * @since 3.0 * @see BindingFactory#getBinding(String) */ public class NoSuchBindingException extends RuntimeException { diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java index df47862eb87..a6aba4a16ff 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/FormatterRegistry.java @@ -22,6 +22,7 @@ import org.springframework.ui.format.Formatter; /** * A centralized registry of Formatters indexed by property types. * TODO - consider moving to ui.format + * TODO - consider a general add(Formatter) method for simplicity * @author Keith Donald * @since 3.0 */ @@ -47,6 +48,13 @@ public interface FormatterRegistry { */ void add(Class propertyType, Formatter formatter); + /** + * Adds a Formatter that will format the values of properties of the provided type. + * @param propertyType the type + * @param formatter the formatter + */ + void add(GenericCollectionPropertyType propertyType, Formatter formatter); + /** * Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation. * @param factory the annotation formatter factory 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 0f01c3cffd9..20ffbf9d0cd 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,7 +29,6 @@ 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; @@ -145,6 +144,17 @@ public class GenericBinder implements Binder { public void registerFormatter(Class propertyType, Formatter formatter) { formatterRegistry.add(propertyType, formatter); } + + /** + * Register a Formatter to format the model properties of a specific property type. + * Convenience method that calls {@link FormatterRegistry#add(GenericCollectionPropertyType, Formatter)} internally. + * @param propertyType the model property type + * @param formatter the formatter + */ + public void registerFormatter(GenericCollectionPropertyType propertyType, Formatter formatter) { + formatterRegistry.add(propertyType, formatter); + } + /** * Register a FormatterFactory that creates Formatter instances as required to format model properties annotated with a specific annotation. @@ -337,6 +347,7 @@ public class GenericBinder implements Binder { } catch (ExpressionException e) { throw new IllegalStateException("Failed to get property expression value - this should not happen", e); } + // TODO if collection we want to format as a single string, need collection formatter return format(value); } @@ -406,6 +417,9 @@ public class GenericBinder implements Binder { private BindingResult setStringValue(String formatted) { Object parsed; try { + // if binding to a collection we may want collection formatter to convert String to Collection + // alternatively, we could map value to a single element e.g. String -> Address via AddressFormatter, which would bind to addresses[0] + // probably want to give preference to collection formatter if one is registered Formatter formatter = getFormatter(); parsed = formatter.parse(formatted, LocaleContextHolder.getLocale()); } catch (ParseException e) { diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericCollectionPropertyType.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericCollectionPropertyType.java new file mode 100644 index 00000000000..0bc7f964b7a --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericCollectionPropertyType.java @@ -0,0 +1,64 @@ +/* + * 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.support; + +/** + * Specifies the element type of a generic collection type. + * @author Keith Donald + * @since 3.0 + */ +public class GenericCollectionPropertyType { + + private Class collectionType; + + private Class elementType; + + /** + * Creates a new generic collection property type + * @param collectionType the collection type + * @param elementType the element type + */ + public GenericCollectionPropertyType(Class collectionType, Class elementType) { + this.collectionType = collectionType; + this.elementType = elementType; + } + + /** + * The collection type. + */ + public Class getCollectionType() { + return collectionType; + } + + /** + * The element type. + */ + public Class getElementType() { + return elementType; + } + + public boolean equals(Object o) { + if (!(o instanceof GenericCollectionPropertyType)) { + return false; + } + GenericCollectionPropertyType type = (GenericCollectionPropertyType) o; + return collectionType.equals(type.collectionType) && elementType.equals(type.elementType); + } + + public int hashCode() { + return collectionType.hashCode() + elementType.hashCode(); + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java index 1e32d1c3e81..7ab1151375c 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericFormatterRegistry.java @@ -42,6 +42,8 @@ public class GenericFormatterRegistry implements FormatterRegistry { private Map typeFormatters = new ConcurrentHashMap(); + private Map collectionTypeFormatters = new ConcurrentHashMap(); + private Map annotationFormatters = new HashMap(); public Formatter getFormatter(TypeDescriptor propertyType) { @@ -52,8 +54,19 @@ public class GenericFormatterRegistry implements FormatterRegistry { return factory.getFormatter(a); } } - Class type = getType(propertyType); - Formatter formatter = typeFormatters.get(type); + Formatter formatter = null; + Class type; + if (propertyType.isCollection()) { + formatter = collectionTypeFormatters.get(new GenericCollectionPropertyType(propertyType.getType(), propertyType.getElementType())); + if (formatter != null) { + return formatter; + } else { + type = propertyType.getElementType(); + } + } else { + type = propertyType.getType(); + } + formatter = typeFormatters.get(type); if (formatter != null) { return formatter; } else { @@ -84,6 +97,10 @@ public class GenericFormatterRegistry implements FormatterRegistry { } } + public void add(GenericCollectionPropertyType propertyType, Formatter formatter) { + collectionTypeFormatters.put(propertyType, formatter); + } + public void add(AnnotationFormatterFactory factory) { annotationFormatters.put(getAnnotationType(factory), factory); } @@ -109,14 +126,6 @@ public class GenericFormatterRegistry implements FormatterRegistry { + factory.getClass().getName() + "]; does the factory parameterize the generic type?"); } - private Class getType(TypeDescriptor descriptor) { - if (descriptor.isArray() || descriptor.isCollection()) { - return descriptor.getElementType(); - } else { - return descriptor.getType(); - } - } - private Class getParameterClass(Type parameterType, Class converterClass) { if (parameterType instanceof TypeVariable) { parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java index 9a56ae0eb21..bac18638852 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java @@ -23,6 +23,8 @@ import java.lang.annotation.Target; /** * A type that can be formatted as a String for display in a UI. + * @author Keith Donald + * @since 3.0 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) 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 f67bacba03b..65ae4c73640 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 @@ -261,7 +261,6 @@ public class GenericBinderTests { 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); @@ -278,8 +277,9 @@ public class GenericBinderTests { } @Test - public void getCollectionAsSingleValue() { - binder.addBinding("addresses").formatWith(new AddressListFormatter()); + public void getListAsSingleValue() { + binder.addBinding("addresses"); + binder.registerFormatter(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter()); Address address1 = new Address(); address1.setStreet("s1"); address1.setCity("c1");