collection property type formatters
This commit is contained in:
parent
2be6ce0407
commit
0c1c5fffba
|
|
@ -18,6 +18,7 @@ package org.springframework.ui.binding;
|
||||||
/**
|
/**
|
||||||
* A factory for model property bindings.
|
* A factory for model property bindings.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public interface BindingFactory {
|
public interface BindingFactory {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
* Exception thrown by a Binder when a required source value is missing unexpectedly from the sourceValues map.
|
||||||
* Indicates a client configuration error.
|
* Indicates a client configuration error.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
|
* @since 3.0
|
||||||
* @see Binder#bind(Map)
|
* @see Binder#bind(Map)
|
||||||
*/
|
*/
|
||||||
public class MissingSourceValuesException extends RuntimeException {
|
public class MissingSourceValuesException extends RuntimeException {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package org.springframework.ui.binding;
|
||||||
/**
|
/**
|
||||||
* Thrown by a BindingFactory when no binding to a property exists.
|
* Thrown by a BindingFactory when no binding to a property exists.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
|
* @since 3.0
|
||||||
* @see BindingFactory#getBinding(String)
|
* @see BindingFactory#getBinding(String)
|
||||||
*/
|
*/
|
||||||
public class NoSuchBindingException extends RuntimeException {
|
public class NoSuchBindingException extends RuntimeException {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import org.springframework.ui.format.Formatter;
|
||||||
/**
|
/**
|
||||||
* A centralized registry of Formatters indexed by property types.
|
* A centralized registry of Formatters indexed by property types.
|
||||||
* TODO - consider moving to ui.format
|
* TODO - consider moving to ui.format
|
||||||
|
* TODO - consider a general add(Formatter) method for simplicity
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -47,6 +48,13 @@ public interface FormatterRegistry {
|
||||||
*/
|
*/
|
||||||
void add(Class<?> propertyType, Formatter<?> formatter);
|
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.
|
* Adds a AnnotationFormatterFactory that will format values of properties annotated with a specific annotation.
|
||||||
* @param factory the annotation formatter factory
|
* @param factory the annotation formatter factory
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.expression.MapAccessor;
|
import org.springframework.context.expression.MapAccessor;
|
||||||
|
|
@ -145,6 +144,17 @@ public class GenericBinder implements Binder {
|
||||||
public void registerFormatter(Class<?> propertyType, Formatter<?> formatter) {
|
public void registerFormatter(Class<?> propertyType, Formatter<?> formatter) {
|
||||||
formatterRegistry.add(propertyType, 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.
|
* 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) {
|
} catch (ExpressionException e) {
|
||||||
throw new IllegalStateException("Failed to get property expression value - this should not happen", 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);
|
return format(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -406,6 +417,9 @@ public class GenericBinder implements Binder {
|
||||||
private BindingResult setStringValue(String formatted) {
|
private BindingResult setStringValue(String formatted) {
|
||||||
Object parsed;
|
Object parsed;
|
||||||
try {
|
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();
|
Formatter formatter = getFormatter();
|
||||||
parsed = formatter.parse(formatted, LocaleContextHolder.getLocale());
|
parsed = formatter.parse(formatted, LocaleContextHolder.getLocale());
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||||
|
|
||||||
private Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
|
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>();
|
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
||||||
|
|
||||||
public Formatter<?> getFormatter(TypeDescriptor<?> propertyType) {
|
public Formatter<?> getFormatter(TypeDescriptor<?> propertyType) {
|
||||||
|
|
@ -52,8 +54,19 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
||||||
return factory.getFormatter(a);
|
return factory.getFormatter(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Class<?> type = getType(propertyType);
|
Formatter<?> formatter = null;
|
||||||
Formatter<?> formatter = typeFormatters.get(type);
|
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) {
|
if (formatter != null) {
|
||||||
return formatter;
|
return formatter;
|
||||||
} else {
|
} 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) {
|
public void add(AnnotationFormatterFactory<?, ?> factory) {
|
||||||
annotationFormatters.put(getAnnotationType(factory), 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?");
|
+ 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) {
|
private Class getParameterClass(Type parameterType, Class converterClass) {
|
||||||
if (parameterType instanceof TypeVariable) {
|
if (parameterType instanceof TypeVariable) {
|
||||||
parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass);
|
parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass);
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type that can be formatted as a String for display in a UI.
|
* A type that can be formatted as a String for display in a UI.
|
||||||
|
* @author Keith Donald
|
||||||
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
@Target({ElementType.TYPE})
|
@Target({ElementType.TYPE})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,6 @@ public class GenericBinderTests {
|
||||||
Map<String, String> values = new LinkedHashMap<String, String>();
|
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");
|
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);
|
BindingResults results = binder.bind(values);
|
||||||
System.out.println(results);
|
|
||||||
Assert.assertEquals(3, bean.addresses.size());
|
Assert.assertEquals(3, bean.addresses.size());
|
||||||
assertEquals("4655 Macy Lane", bean.addresses.get(0).street);
|
assertEquals("4655 Macy Lane", bean.addresses.get(0).street);
|
||||||
assertEquals("Melbourne", bean.addresses.get(0).city);
|
assertEquals("Melbourne", bean.addresses.get(0).city);
|
||||||
|
|
@ -278,8 +277,9 @@ public class GenericBinderTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getCollectionAsSingleValue() {
|
public void getListAsSingleValue() {
|
||||||
binder.addBinding("addresses").formatWith(new AddressListFormatter());
|
binder.addBinding("addresses");
|
||||||
|
binder.registerFormatter(new GenericCollectionPropertyType(List.class, Address.class), new AddressListFormatter());
|
||||||
Address address1 = new Address();
|
Address address1 = new Address();
|
||||||
address1.setStreet("s1");
|
address1.setStreet("s1");
|
||||||
address1.setCity("c1");
|
address1.setCity("c1");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue