collection property type formatters

This commit is contained in:
Keith Donald 2009-07-09 05:18:15 +00:00
parent 2be6ce0407
commit 0c1c5fffba
9 changed files with 114 additions and 14 deletions

View File

@ -18,6 +18,7 @@ package org.springframework.ui.binding;
/**
* A factory for model property bindings.
* @author Keith Donald
* @since 3.0
*/
public interface BindingFactory {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -42,6 +42,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
private Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
private Map<GenericCollectionPropertyType, Formatter> collectionTypeFormatters = new ConcurrentHashMap<GenericCollectionPropertyType, Formatter>();
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
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 <A> 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);

View File

@ -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)

View File

@ -261,7 +261,6 @@ public class GenericBinderTests {
Map<String, String> values = new LinkedHashMap<String, String>();
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");