提交 0a78287a 编写于 作者: K Keith Donald

formatters now plugged into unified type conversion api with formatter specific registry spi

上级 2e4fa28c
...@@ -19,14 +19,14 @@ import java.lang.annotation.Annotation; ...@@ -19,14 +19,14 @@ import java.lang.annotation.Annotation;
import java.util.Set; import java.util.Set;
/** /**
* A factory that creates formatters to format values of fields annotated with a particular format {@link Annotation}. * A factory that creates formatters to format values of fields annotated with a particular {@link Annotation}.
* *
* <p>For example, a <code>DateTimeFormatAnnotationFormatterFactory</code> might create a formatter * <p>For example, a <code>DateTimeFormatAnnotationFormatterFactory</code> might create a formatter
* that formats a <code>Date</code> objects set on properties annotated with <code>@DateFormat</code>. * that formats <code>Date</code> values set on fields annotated with <code>@DateTimeFormat</code>.
* *
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @param <A> the type of Annotation that should trigger property formatting * @param <A> the annotation type that should trigger formatting
*/ */
public interface AnnotationFormatterFactory<A extends Annotation> { public interface AnnotationFormatterFactory<A extends Annotation> {
...@@ -36,17 +36,19 @@ public interface AnnotationFormatterFactory<A extends Annotation> { ...@@ -36,17 +36,19 @@ public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes(); Set<Class<?>> getFieldTypes();
/** /**
* Get the Printer to print the value of a property of <code>fieldType</code> annotated with <code>annotation</code>. * Get the Printer to print the value of a field of <code>fieldType</code> annotated with <code>annotation</code>.
* If the type &lt;T&gt; the printer accepts is not assignable to <code>fieldType</code>, a coersion from <code>fieldType</code> to &lt;T&gt; will be attempted before the Printer is invoked.
* @param annotation the annotation instance * @param annotation the annotation instance
* @param fieldType the type of property being annotated * @param fieldType the type of field that was annotated
* @return the printer * @return the printer
*/ */
Printer<?> getPrinter(A annotation, Class<?> fieldType); Printer<?> getPrinter(A annotation, Class<?> fieldType);
/** /**
* Get the Parser to parse the printed value of a property of <code>fieldType</code> annotated with <code>annotation</code>. * Get the Parser to parse a submitted value for a field of <code>fieldType</code> annotated with <code>annotation</code>.
* If the object the parser returns is not assignable to <code>fieldType</code>, a coersion to <code>fieldType</code> will be attempted before the field is set.
* @param annotation the annotation instance * @param annotation the annotation instance
* @param fieldType the type of field being annotated * @param fieldType the type of field that was annotated
* @return the parser * @return the parser
*/ */
Parser<?> getParser(A annotation, Class<?> fieldType); Parser<?> getParser(A annotation, Class<?> fieldType);
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package org.springframework.ui.format; package org.springframework.ui.format;
import java.lang.annotation.Annotation;
import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.ConverterRegistry;
/** /**
...@@ -54,12 +56,12 @@ public interface FormatterRegistry { ...@@ -54,12 +56,12 @@ public interface FormatterRegistry {
* Adds a Formatter to format fields annotated with a specific format annotation. * Adds a Formatter to format fields annotated with a specific format annotation.
* @param annotationFormatterFactory the annotation formatter factory to add * @param annotationFormatterFactory the annotation formatter factory to add
*/ */
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<?> annotationFormatterFactory); void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
/** /**
* Returns the registry of Converters that coerse field values to types required by Formatters. * Returns the registry of Converters that coerse field values to types required by Formatters.
* Allows clients to register their own custom converters. * Allows clients to register their own custom converters directly.
* For example, a date/time formatting configuration might expect a java.util.Date field value to be converted to a Long for formatting. * For example, a date/time formatting configuration might expect a java.util.Date field value to be coersed to a Long for formatting.
* Registering a simpler DateToLongConverter allievates the need to register multiple formatters for closely related types. * Registering a simpler DateToLongConverter allievates the need to register multiple formatters for closely related types.
* @return the converter registry, allowing new Converters to be registered * @return the converter registry, allowing new Converters to be registered
*/ */
......
...@@ -41,7 +41,7 @@ abstract class AbstractDateTimeAnnotationFormatterFactory<A extends Annotation> ...@@ -41,7 +41,7 @@ abstract class AbstractDateTimeAnnotationFormatterFactory<A extends Annotation>
private final Set<Class<?>> propertyTypes; private final Set<Class<?>> propertyTypes;
public AbstractDateTimeAnnotationFormatterFactory() { public AbstractDateTimeAnnotationFormatterFactory() {
this.propertyTypes = Collections.unmodifiableSet(createPropertyTypes()); this.propertyTypes = Collections.unmodifiableSet(createFieldTypes());
} }
public Set<Class<?>> getFieldTypes() { public Set<Class<?>> getFieldTypes() {
...@@ -76,7 +76,7 @@ abstract class AbstractDateTimeAnnotationFormatterFactory<A extends Annotation> ...@@ -76,7 +76,7 @@ abstract class AbstractDateTimeAnnotationFormatterFactory<A extends Annotation>
// internal helpers // internal helpers
private Set<Class<?>> createPropertyTypes() { private Set<Class<?>> createFieldTypes() {
Set<Class<?>> propertyTypes = new HashSet<Class<?>>(5); Set<Class<?>> propertyTypes = new HashSet<Class<?>>(5);
propertyTypes.add(ReadableInstant.class); propertyTypes.add(ReadableInstant.class);
propertyTypes.add(ReadablePartial.class); propertyTypes.add(ReadablePartial.class);
......
...@@ -28,7 +28,7 @@ import org.springframework.core.convert.converter.Converter; ...@@ -28,7 +28,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.ConverterRegistry;
/** /**
* Installs lower-level type converters required to integrate Joda Time support into Spring's formatting and property binding systems. * Installs lower-level type converters required to integrate Joda Time support into Spring's formatting and field binding systems.
* @author Keith Donald * @author Keith Donald
*/ */
final class JodaTimeConverters { final class JodaTimeConverters {
...@@ -49,62 +49,88 @@ final class JodaTimeConverters { ...@@ -49,62 +49,88 @@ final class JodaTimeConverters {
registry.addConverter(new DateTimeToDateConverter()); registry.addConverter(new DateTimeToDateConverter());
registry.addConverter(new DateTimeToCalendarConverter()); registry.addConverter(new DateTimeToCalendarConverter());
registry.addConverter(new DateToLongConverter()); registry.addConverter(new DateToLongConverter());
registry.addConverter(new CalendarToDateTimeConverter()); registry.addConverter(new CalendarToReadableInstantConverter());
} }
// internal helpers // internal helpers
// used when binding a parsed DateTime to a LocalDate property /**
* Used when binding a parsed DateTime to a LocalDate field.
* @see DateTimeParser
**/
private static class DateTimeToLocalDateConverter implements Converter<DateTime, LocalDate> { private static class DateTimeToLocalDateConverter implements Converter<DateTime, LocalDate> {
public LocalDate convert(DateTime source) { public LocalDate convert(DateTime source) {
return source.toLocalDate(); return source.toLocalDate();
} }
} }
// used when binding a parsed DateTime to a LocalTime property /**
* Used when binding a parsed DateTime to a LocalTime field.
* @see DateTimeParser
*/
private static class DateTimeToLocalTimeConverter implements Converter<DateTime, LocalTime> { private static class DateTimeToLocalTimeConverter implements Converter<DateTime, LocalTime> {
public LocalTime convert(DateTime source) { public LocalTime convert(DateTime source) {
return source.toLocalTime(); return source.toLocalTime();
} }
} }
// used when binding a parsed DateTime to a LocalDateTime property /**
* Used when binding a parsed DateTime to a LocalDateTime field.
* @see DateTimeParser
*/
private static class DateTimeToLocalDateTimeConverter implements Converter<DateTime, LocalDateTime> { private static class DateTimeToLocalDateTimeConverter implements Converter<DateTime, LocalDateTime> {
public LocalDateTime convert(DateTime source) { public LocalDateTime convert(DateTime source) {
return source.toLocalDateTime(); return source.toLocalDateTime();
} }
} }
// used when binding a parsed DateTime to a DateMidnight property /**
* Used when binding a parsed DateTime to a DateMidnight field.
* @see DateTimeParser
*/
private static class DateTimeToDateMidnightConverter implements Converter<DateTime, DateMidnight> { private static class DateTimeToDateMidnightConverter implements Converter<DateTime, DateMidnight> {
public DateMidnight convert(DateTime source) { public DateMidnight convert(DateTime source) {
return source.toDateMidnight(); return source.toDateMidnight();
} }
} }
// used when binding a parsed DateTime to a java.util.Date property /**
* Used when binding a parsed DateTime to a java.util.Date field.
* @see DateTimeParser
*/
private static class DateTimeToDateConverter implements Converter<DateTime, Date> { private static class DateTimeToDateConverter implements Converter<DateTime, Date> {
public Date convert(DateTime source) { public Date convert(DateTime source) {
return source.toDate(); return source.toDate();
} }
} }
// used when binding a parsed DateTime to a java.util.Calendar property /**
* Used when binding a parsed DateTime to a java.util.Calendar field.
* @see DateTimeParser
*/
private static class DateTimeToCalendarConverter implements Converter<DateTime, Calendar> { private static class DateTimeToCalendarConverter implements Converter<DateTime, Calendar> {
public Calendar convert(DateTime source) { public Calendar convert(DateTime source) {
return source.toGregorianCalendar(); return source.toGregorianCalendar();
} }
} }
// used when formatting a java.util.Date property with a MillisecondInstantPrinter /**
* Used when printing a java.util.Date field with a MillisecondInstantPrinter.
* @see MillisecondInstantPrinter
* @see DateTimeFormatAnnotationFormatterFactory
*/
private static class DateToLongConverter implements Converter<Date, Long> { private static class DateToLongConverter implements Converter<Date, Long> {
public Long convert(Date source) { public Long convert(Date source) {
return source.getTime(); return source.getTime();
} }
} }
// used when formatting a java.util.Calendar property with a ReadableInstantPrinter /**
private static class CalendarToDateTimeConverter implements Converter<Calendar, ReadableInstant> { * Used when printing a java.util.Calendar field with a ReadableInstantPrinter.
* @see MillisecondInstantPrinter
* @see DateTimeFormatAnnotationFormatterFactory
*/
private static class CalendarToReadableInstantConverter implements Converter<Calendar, ReadableInstant> {
public ReadableInstant convert(Calendar source) { public ReadableInstant convert(Calendar source) {
return new DateTime(source); return new DateTime(source);
} }
......
...@@ -111,7 +111,7 @@ public class JodaTimeFormattingConfigurer { ...@@ -111,7 +111,7 @@ public class JodaTimeFormattingConfigurer {
Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter); Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter);
this.formatterRegistry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser); this.formatterRegistry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser);
this.formatterRegistry.addFormatterForFieldType(Calendar.class, readableInstantPrinter, dateTimeParser); this.formatterRegistry.addFormatterForFieldType(Calendar.class, readableInstantPrinter, dateTimeParser);
this.formatterRegistry.addFormatterForFieldType(Calendar.class, new MillisecondInstantPrinter(jodaDateTimeFormatter), dateTimeParser); this.formatterRegistry.addFormatterForFieldType(Date.class, new MillisecondInstantPrinter(jodaDateTimeFormatter), dateTimeParser);
this.formatterRegistry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory()); this.formatterRegistry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
} }
......
...@@ -13,222 +13,188 @@ ...@@ -13,222 +13,188 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format.support; package org.springframework.ui.format.support;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.text.ParseException; import java.text.ParseException;
import java.util.LinkedList; import java.util.Set;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.ConditionalGenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.support.GenericConverter;
import org.springframework.ui.format.AnnotationFormatterFactory; import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry; import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.FormattingService;
import org.springframework.ui.format.Parser; import org.springframework.ui.format.Parser;
import org.springframework.ui.format.Printer; import org.springframework.ui.format.Printer;
import org.springframework.util.Assert;
/** /**
* A generic implementation of {@link FormattingService} suitable for use in most environments. * A ConversionService implementation designed to be configured as a {@link FormatterRegistry}..
* Is a {@link FormatterRegistry} to allow for registration of field formatting logic.
*
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
public class GenericFormattingService implements FormattingService, FormatterRegistry { public class FormattingConversionService implements FormatterRegistry, ConversionService {
private final Map<Class<?>, GenericFormatter> typeFormatters = new ConcurrentHashMap<Class<?>, GenericFormatter>();
private final Map<Class<? extends Annotation>, GenericAnnotationFormatterFactory> annotationFormatters = new ConcurrentHashMap<Class<? extends Annotation>, GenericAnnotationFormatterFactory>();
private GenericConversionService conversionService = new GenericConversionService(); private GenericConversionService conversionService = new GenericConversionService();
/** /**
* Configure a parent of the type conversion service that will be used to coerce objects to types required for formatting. * Creates a new FormattingConversionService, initially with no Formatters registered.
* A {@link DefaultConversionService} is configured as the parent conversion service to support primitive type conversion.
*/ */
public void setParentConversionService(ConversionService parentConversionService) { public FormattingConversionService() {
this.conversionService.setParent(parentConversionService); this.conversionService.setParent(new DefaultConversionService());
} }
// implementing FormattingService /**
* Creates a new FormattingConversionService, initially with no Formatters registered.
public String print(Object fieldValue, TypeDescriptor fieldType, Locale locale) { * The conversion logic contained in the parent is merged with this service.
return getFormatter(fieldType).print(fieldValue, fieldType, locale); */
} public FormattingConversionService(ConversionService parent) {
this.conversionService.setParent(parent);
public Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException {
return getFormatter(fieldType).parse(submittedValue, fieldType, locale);
} }
// implementing FormatterRegistry // implementing FormattingRegistry
public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) { public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
Class<?> printerObjectType = resolvePrinterObjectType(printer); this.conversionService.addGenericConverter(fieldType, String.class, new PrinterConverter(printer, this.conversionService));
Class<?> parserObjectType = resolveParserObjectType(parser); this.conversionService.addGenericConverter(String.class, fieldType, new ParserConverter(parser, this.conversionService));
this.typeFormatters.put(fieldType, new GenericFormatter(printerObjectType, printer, parserObjectType, parser));
} }
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
Class<?> formatterObjectType = resolveFormatterObjectType(formatter); this.conversionService.addGenericConverter(fieldType, String.class, new PrinterConverter(formatter, this.conversionService));
this.typeFormatters.put(fieldType, new GenericFormatter(formatterObjectType, formatter, formatterObjectType, formatter)); this.conversionService.addGenericConverter(String.class, fieldType, new ParserConverter(formatter, this.conversionService));
} }
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<?> annotationFormatterFactory) { @SuppressWarnings("unchecked")
Class<? extends Annotation> annotationType = resolveAnnotationType(annotationFormatterFactory); public void addFormatterForFieldAnnotation(final AnnotationFormatterFactory annotationFormatterFactory) {
final Class<? extends Annotation> annotationType = resolveAnnotationType(annotationFormatterFactory);
if (annotationType == null) { if (annotationType == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory [" "Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory ["
+ annotationFormatterFactory.getClass().getName() + annotationFormatterFactory.getClass().getName()
+ "]; does the factory parameterize the <A extends Annotation> generic type?"); + "]; does the factory parameterize the <A extends Annotation> generic type?");
}
Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
for (Class<?> fieldType : fieldTypes) {
this.conversionService.addGenericConverter(fieldType, String.class, new ConditionalGenericConverter() {
public boolean matches(TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) {
return sourceFieldType.getAnnotation(annotationType) != null;
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Printer<?> printer = annotationFormatterFactory.getPrinter(sourceType.getAnnotation(annotationType), targetType.getType());
return new PrinterConverter(printer, conversionService).convert(source, sourceType, targetType);
}
});
this.conversionService.addGenericConverter(String.class, fieldType, new ConditionalGenericConverter() {
public boolean matches(TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) {
return targetFieldType.getAnnotation(annotationType) != null;
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Parser<?> parser = annotationFormatterFactory.getParser(targetType.getAnnotation(annotationType), targetType.getType());
return new ParserConverter(parser, conversionService).convert(source, sourceType, targetType);
}
});
} }
this.annotationFormatters.put(annotationType, new GenericAnnotationFormatterFactory(annotationFormatterFactory));
} }
public ConverterRegistry getConverterRegistry() { public ConverterRegistry getConverterRegistry() {
return this.conversionService; return this.conversionService;
} }
// internal helpers // implementing ConverisonService
private Class<?> resolveParserObjectType(Parser<?> parser) {
return GenericTypeResolver.resolveTypeArgument(parser.getClass(), Parser.class);
}
private Class<?> resolvePrinterObjectType(Printer<?> printer) { public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class); return canConvert(TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType));
} }
private Class<?> resolveFormatterObjectType(Formatter<?> formatter) {
return GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Class<? extends Annotation> resolveAnnotationType(AnnotationFormatterFactory<?> annotationFormatterFactory) { public <T> T convert(Object source, Class<T> targetType) {
return (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
} }
private GenericFormatter getFormatter(TypeDescriptor fieldType) { public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(fieldType, "Field TypeDescriptor is required"); return this.conversionService.canConvert(sourceType, targetType);
GenericFormatter formatter = findFormatterForAnnotatedField(fieldType);
Class<?> fieldObjectType = fieldType.getObjectType();
if (formatter == null) {
formatter = findFormatterForFieldType(fieldObjectType);
}
return formatter;
} }
private GenericFormatter findFormatterForAnnotatedField(TypeDescriptor fieldType) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
for (Annotation annotation : fieldType.getAnnotations()) { return this.conversionService.convert(source, sourceType, targetType);
GenericFormatter formatter = findFormatterForAnnotation(annotation, fieldType.getObjectType());
if (formatter != null) {
return formatter;
}
}
return null;
} }
private GenericFormatter findFormatterForAnnotation(Annotation annotation, Class<?> fieldType) { // internal helpers
Class<? extends Annotation> annotationType = annotation.annotationType();
GenericAnnotationFormatterFactory factory = this.annotationFormatters.get(annotationType);
if (factory != null) {
return factory.getFormatter(annotation, fieldType);
} else {
return null;
}
}
private GenericFormatter findFormatterForFieldType(Class<?> fieldType) { @SuppressWarnings("unchecked")
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); private Class<? extends Annotation> resolveAnnotationType(AnnotationFormatterFactory<?> annotationFormatterFactory) {
classQueue.addFirst(fieldType); return (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
GenericFormatter formatter = this.typeFormatters.get(currentClass);
if (formatter != null) {
return formatter;
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass());
}
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
}
return null;
} }
@SuppressWarnings("unchecked") private static class PrinterConverter implements GenericConverter {
private class GenericFormatter {
private TypeDescriptor printerObjectType; private TypeDescriptor printerObjectType;
@SuppressWarnings("unchecked")
private Printer printer; private Printer printer;
private Parser parser; private ConversionService conversionService;
public GenericFormatter(Class<?> printerObjectType, Printer<?> printer, Class<?> parserObjectType, Parser<?> parser) { public PrinterConverter(Printer<?> printer, ConversionService conversionService) {
this.printerObjectType = TypeDescriptor.valueOf(printerObjectType); this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
this.printer = printer; this.printer = printer;
this.parser = parser; this.conversionService = conversionService;
}
public String print(Object fieldValue, TypeDescriptor fieldType, Locale locale) {
if (!fieldType.isAssignableTo(this.printerObjectType)) {
fieldValue = GenericFormattingService.this.conversionService.convert(fieldValue, fieldType, this.printerObjectType);
}
return fieldType != null ? this.printer.print(fieldValue, locale) : "";
} }
public Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException { @SuppressWarnings("unchecked")
if (submittedValue.isEmpty()) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return null; if (!sourceType.isAssignableTo(this.printerObjectType)) {
} source = this.conversionService.convert(source, sourceType, this.printerObjectType);
Object parsedValue = this.parser.parse(submittedValue, locale);
TypeDescriptor parsedObjectType = TypeDescriptor.valueOf(parsedValue.getClass());
if (!parsedObjectType.isAssignableTo(fieldType)) {
parsedValue = GenericFormattingService.this.conversionService.convert(parsedValue, parsedObjectType, fieldType);
} }
return parsedValue; return source != null ? this.printer.print(source, LocaleContextHolder.getLocale()) : "";
} }
private Class<?> resolvePrinterObjectType(Printer<?> printer) {
return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
}
} }
@SuppressWarnings("unchecked") private static class ParserConverter implements GenericConverter {
private class GenericAnnotationFormatterFactory {
private AnnotationFormatterFactory annotationFormatterFactory;
public GenericAnnotationFormatterFactory(AnnotationFormatterFactory<?> annotationFormatterFactory) { @SuppressWarnings("unchecked")
this.annotationFormatterFactory = annotationFormatterFactory; private Parser parser;
}
public GenericFormatter getFormatter(Annotation annotation, Class<?> fieldType) { private ConversionService conversionService;
Printer<?> printer = this.annotationFormatterFactory.getPrinter(annotation, fieldType);
Parser<?> parser = this.annotationFormatterFactory.getParser(annotation, fieldType);
return new GenericFormatter(getPrinterObjectType(printer, fieldType), printer, getParserObjectType(parser, fieldType), parser);
}
// internal helpers public ParserConverter(Parser<?> parser, ConversionService conversionService) {
this.parser = parser;
private Class<?> getPrinterObjectType(Printer<?> printer, Class<?> fieldType) { this.conversionService = conversionService;
// TODO cache
return resolvePrinterObjectType(printer);
} }
private Class<?> getParserObjectType(Parser<?> parser, Class<?> fieldType) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
// TODO cache String submittedValue = (String) source;
return resolveParserObjectType(parser); if (submittedValue.isEmpty()) {
return null;
}
Object parsedValue;
try {
parsedValue = this.parser.parse(submittedValue, LocaleContextHolder.getLocale());
} catch (ParseException e) {
throw new ConversionFailedException(sourceType, targetType, source, e);
}
TypeDescriptor parsedObjectType = TypeDescriptor.valueOf(parsedValue.getClass());
if (!parsedObjectType.isAssignableTo(targetType)) {
try {
parsedValue = this.conversionService.convert(parsedValue, parsedObjectType, targetType);
} catch (ConversionFailedException e) {
throw new ConversionFailedException(sourceType, targetType, source, e);
}
}
return parsedValue;
} }
} }
} }
/*
* Copyright 2002-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.format.support;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.support.GenericConverter;
import org.springframework.ui.format.FormattingService;
/**
* Adapter that exposes a {@link ConversionService} reference for a given {@link FormattingService},
* retrieving the current Locale from {@link LocaleContextHolder}.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class FormattingConversionServiceAdapter extends GenericConversionService {
private final FormattingService formattingService;
public FormattingConversionServiceAdapter(FormattingService formattingService) {
this.formattingService = formattingService;
addGenericConverter(String.class, Object.class, new GenericConverter() {
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return FormattingConversionServiceAdapter.this.formattingService.parse((String) source, targetType, LocaleContextHolder.getLocale());
} catch (ParseException e) {
throw new ConversionFailedException(sourceType, targetType, source, e);
}
}
});
addGenericConverter(Object.class, String.class, new GenericConverter() {
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return FormattingConversionServiceAdapter.this.formattingService.print(source, targetType, LocaleContextHolder.getLocale());
}
});
}
}
...@@ -17,11 +17,8 @@ ...@@ -17,11 +17,8 @@
package org.springframework.ui.format.support; package org.springframework.ui.format.support;
import java.beans.PropertyEditorSupport; import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.format.FormattingService;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
...@@ -34,35 +31,29 @@ import org.springframework.util.Assert; ...@@ -34,35 +31,29 @@ import org.springframework.util.Assert;
*/ */
public class FormattingPropertyEditorAdapter extends PropertyEditorSupport { public class FormattingPropertyEditorAdapter extends PropertyEditorSupport {
private final FormattingService formattingService; private final ConversionService conversionService;
private final Class<?> fieldType;
private final TypeDescriptor fieldType;
/** /**
* Create a new FormattingPropertyEditorAdapter for the given Formatter. * Create a new FormattingPropertyEditorAdapter for the given Formatter.
* @param formatter the Formatter to wrap * @param formatter the Formatter to wrap
*/ */
public FormattingPropertyEditorAdapter(FormattingService formattingService, Class<?> fieldType) { public FormattingPropertyEditorAdapter(ConversionService formattingService, Class<?> fieldType) {
Assert.notNull(formattingService, "FormattingService must not be null"); Assert.notNull(formattingService, "ConversionService must not be null");
Assert.notNull(formattingService, "FieldType must not be null"); Assert.notNull(formattingService, "FieldType must not be null");
this.formattingService = formattingService; this.conversionService = formattingService;
this.fieldType = TypeDescriptor.valueOf(fieldType); this.fieldType = fieldType;
} }
@Override @Override
public void setAsText(String text) throws IllegalArgumentException { public void setAsText(String text) throws IllegalArgumentException {
try { setValue(this.conversionService.convert(text, this.fieldType));
setValue(this.formattingService.parse(text, this.fieldType, LocaleContextHolder.getLocale()));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Failed to parse formatted value", ex);
}
} }
@Override @Override
public String getAsText() { public String getAsText() {
return this.formattingService.print(getValue(), this.fieldType, LocaleContextHolder.getLocale()); return this.conversionService.convert(getValue(), String.class);
} }
} }
...@@ -22,10 +22,8 @@ import org.springframework.beans.BeanUtils; ...@@ -22,10 +22,8 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.ConfigurablePropertyAccessor;
import org.springframework.beans.PropertyAccessorUtils; import org.springframework.beans.PropertyAccessorUtils;
import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.format.FormattingService;
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
import org.springframework.ui.format.support.FormattingPropertyEditorAdapter; import org.springframework.ui.format.support.FormattingPropertyEditorAdapter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -43,7 +41,7 @@ import org.springframework.util.Assert; ...@@ -43,7 +41,7 @@ import org.springframework.util.Assert;
*/ */
public abstract class AbstractPropertyBindingResult extends AbstractBindingResult { public abstract class AbstractPropertyBindingResult extends AbstractBindingResult {
private FormattingService formattingService; private ConversionService conversionService;
/** /**
...@@ -56,10 +54,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul ...@@ -56,10 +54,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
} }
public void initFormatting(FormattingService formattingService) { public void initConversion(ConversionService conversionService) {
Assert.notNull(formattingService, "FormattingService must not be null"); Assert.notNull(conversionService, "ConversionService must not be null");
this.formattingService = formattingService; this.conversionService = conversionService;
getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formattingService)); getPropertyAccessor().setConversionService(conversionService);
} }
/** /**
...@@ -116,10 +114,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul ...@@ -116,10 +114,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
return textValue; return textValue;
} }
} }
if (this.formattingService != null) { if (this.conversionService != null) {
// Try custom formatter... // Try custom formatter...
TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField); TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
return this.formattingService.print(value, td, LocaleContextHolder.getLocale()); return this.conversionService.convert(value, td, TypeDescriptor.valueOf(String.class));
} else { } else {
return value; return value;
} }
...@@ -149,11 +147,11 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul ...@@ -149,11 +147,11 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
valueType = getFieldType(field); valueType = getFieldType(field);
} }
PropertyEditor editor = super.findEditor(field, valueType); PropertyEditor editor = super.findEditor(field, valueType);
if (editor == null && this.formattingService != null) { if (editor == null && this.conversionService != null) {
TypeDescriptor td = (field != null ? TypeDescriptor td = (field != null ?
getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)) : getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)) :
TypeDescriptor.valueOf(valueType)); TypeDescriptor.valueOf(valueType));
editor = new FormattingPropertyEditorAdapter(this.formattingService, valueType); editor = new FormattingPropertyEditorAdapter(this.conversionService, valueType);
} }
return editor; return editor;
} }
......
...@@ -34,8 +34,7 @@ import org.springframework.beans.SimpleTypeConverter; ...@@ -34,8 +34,7 @@ import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException; import org.springframework.beans.TypeMismatchException;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.ui.format.FormattingService; import org.springframework.core.convert.ConversionService;
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils; import org.springframework.util.PatternMatchUtils;
...@@ -135,7 +134,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -135,7 +134,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
private Validator validator; private Validator validator;
private FormattingService formattingService; private ConversionService conversionService;
/** /**
...@@ -183,8 +182,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -183,8 +182,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Assert.isNull(this.bindingResult, Assert.isNull(this.bindingResult,
"DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods"); "DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods");
this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName()); this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName());
if (this.formattingService != null) { if (this.conversionService != null) {
this.bindingResult.initFormatting(this.formattingService); this.bindingResult.initConversion(this.conversionService);
} }
} }
...@@ -197,8 +196,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -197,8 +196,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Assert.isNull(this.bindingResult, Assert.isNull(this.bindingResult,
"DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods"); "DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods");
this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName()); this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName());
if (this.formattingService != null) { if (this.conversionService != null) {
this.bindingResult.initFormatting(this.formattingService); this.bindingResult.initConversion(this.conversionService);
} }
} }
...@@ -226,8 +225,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -226,8 +225,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
protected SimpleTypeConverter getSimpleTypeConverter() { protected SimpleTypeConverter getSimpleTypeConverter() {
if (this.typeConverter == null) { if (this.typeConverter == null) {
this.typeConverter = new SimpleTypeConverter(); this.typeConverter = new SimpleTypeConverter();
if (this.formattingService != null) { if (this.conversionService != null) {
this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formattingService)); this.typeConverter.setConversionService(this.conversionService);
} }
} }
return this.typeConverter; return this.typeConverter;
...@@ -461,10 +460,10 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -461,10 +460,10 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
} }
/** /**
* Set the FormattingService to use for field value formatting in preference to JavaBeans PropertyEditors. * Set the ConversionService to use for field value formatting in preference to JavaBeans PropertyEditors.
*/ */
public void setFormattingService(FormattingService formattingService) { public void setConversionService(ConversionService conversionService) {
this.formattingService = formattingService; this.conversionService = conversionService;
} }
//--------------------------------------------------------------------- //---------------------------------------------------------------------
...@@ -492,7 +491,6 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { ...@@ -492,7 +491,6 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
public <T> T convertIfNecessary( public <T> T convertIfNecessary(
Object value, Class<T> requiredType, MethodParameter methodParam) throws TypeMismatchException { Object value, Class<T> requiredType, MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
} }
......
...@@ -25,11 +25,12 @@ import java.util.Locale; ...@@ -25,11 +25,12 @@ import java.util.Locale;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormat;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.jodatime.DateTimeFormatAnnotationFormatterFactory; import org.springframework.ui.format.jodatime.DateTimeFormatAnnotationFormatterFactory;
import org.springframework.ui.format.jodatime.DateTimeParser; import org.springframework.ui.format.jodatime.DateTimeParser;
import org.springframework.ui.format.jodatime.ReadablePartialPrinter; import org.springframework.ui.format.jodatime.ReadablePartialPrinter;
...@@ -42,20 +43,25 @@ import org.springframework.ui.format.number.IntegerFormatter; ...@@ -42,20 +43,25 @@ import org.springframework.ui.format.number.IntegerFormatter;
*/ */
public class GenericFormattingServiceTests { public class GenericFormattingServiceTests {
private GenericFormattingService formattingService; private FormattingConversionService formattingService;
@Before @Before
public void setUp() { public void setUp() {
formattingService = new GenericFormattingService(); formattingService = new FormattingConversionService();
formattingService.setParentConversionService(new DefaultConversionService()); LocaleContextHolder.setLocale(Locale.US);
}
@After
public void tearDown() {
LocaleContextHolder.setLocale(null);
} }
@Test @Test
public void testFormatFieldForTypeWithFormatter() throws ParseException { public void testFormatFieldForTypeWithFormatter() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new IntegerFormatter()); formattingService.addFormatterForFieldType(Number.class, new IntegerFormatter());
String formatted = formattingService.print(new Integer(3), TypeDescriptor.valueOf(Integer.class), Locale.US); String formatted = formattingService.convert(new Integer(3), String.class);
assertEquals("3", formatted); assertEquals("3", formatted);
Integer i = (Integer) formattingService.parse("3", TypeDescriptor.valueOf(Integer.class), Locale.US); Integer i = (Integer) formattingService.convert("3", Integer.class);
assertEquals(new Integer(3), i); assertEquals(new Integer(3), i);
} }
...@@ -68,9 +74,9 @@ public class GenericFormattingServiceTests { ...@@ -68,9 +74,9 @@ public class GenericFormattingServiceTests {
}); });
formattingService.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(DateTimeFormat formattingService.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(DateTimeFormat
.shortDate()), new DateTimeParser(DateTimeFormat.shortDate())); .shortDate()), new DateTimeParser(DateTimeFormat.shortDate()));
String formatted = formattingService.print(new LocalDate(2009, 10, 31), TypeDescriptor.valueOf(LocalDate.class), Locale.US); String formatted = formattingService.convert(new LocalDate(2009, 10, 31), String.class);
assertEquals("10/31/09", formatted); assertEquals("10/31/09", formatted);
LocalDate date = (LocalDate) formattingService.parse("10/31/09", TypeDescriptor.valueOf(LocalDate.class), Locale.US); LocalDate date = (LocalDate) formattingService.convert("10/31/09", LocalDate.class);
assertEquals(new LocalDate(2009, 10, 31), date); assertEquals(new LocalDate(2009, 10, 31), date);
} }
...@@ -85,21 +91,22 @@ public class GenericFormattingServiceTests { ...@@ -85,21 +91,22 @@ public class GenericFormattingServiceTests {
public Date convert(DateTime source) { public Date convert(DateTime source) {
return source.toDate(); return source.toDate();
} }
}); });
formattingService.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory()); formattingService.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
String formatted = formattingService.print(new LocalDate(2009, 10, 31).toDateTimeAtCurrentTime().toDate(), new TypeDescriptor(Model.class.getField("date")), Locale.US); String formatted = (String) formattingService.convert(new LocalDate(2009, 10, 31).toDateTimeAtCurrentTime()
.toDate(), new TypeDescriptor(Model.class.getField("date")), TypeDescriptor.valueOf(String.class));
assertEquals("10/31/09", formatted); assertEquals("10/31/09", formatted);
LocalDate date = new LocalDate(formattingService.parse("10/31/09", new TypeDescriptor(Model.class.getField("date")), Locale.US)); LocalDate date = new LocalDate(formattingService.convert("10/31/09", TypeDescriptor.valueOf(String.class),
new TypeDescriptor(Model.class.getField("date"))));
assertEquals(new LocalDate(2009, 10, 31), date); assertEquals(new LocalDate(2009, 10, 31), date);
} }
private static class Model { private static class Model {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@org.springframework.ui.format.jodatime.DateTimeFormat(dateStyle=FormatStyle.SHORT) @org.springframework.ui.format.jodatime.DateTimeFormat(dateStyle = FormatStyle.SHORT)
public Date date; public Date date;
}
}
} }
...@@ -22,10 +22,6 @@ import java.io.ByteArrayInputStream; ...@@ -22,10 +22,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -49,7 +45,7 @@ import org.springframework.context.support.ResourceBundleMessageSource; ...@@ -49,7 +45,7 @@ import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource; import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.number.DecimalFormatter; import org.springframework.ui.format.number.DecimalFormatter;
import org.springframework.ui.format.support.GenericFormattingService; import org.springframework.ui.format.support.FormattingConversionService;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -303,10 +299,9 @@ public class DataBinderTests extends TestCase { ...@@ -303,10 +299,9 @@ public class DataBinderTests extends TestCase {
public void testBindingWithFormatter() { public void testBindingWithFormatter() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb); DataBinder binder = new DataBinder(tb);
GenericFormattingService formattingService = new GenericFormattingService(); FormattingConversionService conversionService = new FormattingConversionService();
formattingService.setParentConversionService(new DefaultConversionService()); conversionService.addFormatterForFieldType(Float.class, new DecimalFormatter());
formattingService.addFormatterForFieldType(Float.class, new DecimalFormatter()); binder.setConversionService(conversionService);
binder.setFormattingService(formattingService);
MutablePropertyValues pvs = new MutablePropertyValues(); MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue("myFloat", "1,2"); pvs.addPropertyValue("myFloat", "1,2");
......
...@@ -26,6 +26,7 @@ package org.springframework.core.convert; ...@@ -26,6 +26,7 @@ package org.springframework.core.convert;
public interface ConversionService { public interface ConversionService {
/** /**
* TODO - do we really need to support this?
* Returns true if objects of sourceType can be converted to targetType. * Returns true if objects of sourceType can be converted to targetType.
* @param sourceType the source type to convert from (required) * @param sourceType the source type to convert from (required)
* @param targetType the target type to convert to (required) * @param targetType the target type to convert to (required)
...@@ -34,6 +35,7 @@ public interface ConversionService { ...@@ -34,6 +35,7 @@ public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType); boolean canConvert(Class<?> sourceType, Class<?> targetType);
/** /**
* TODO - do we really need to support this?
* Convert the source to targetType. * Convert the source to targetType.
* @param source the source object to convert (may be null) * @param source the source object to convert (may be null)
* @param targetType the target type to convert to (required) * @param targetType the target type to convert to (required)
......
...@@ -283,6 +283,18 @@ public class TypeDescriptor { ...@@ -283,6 +283,18 @@ public class TypeDescriptor {
} }
} }
/**
* Obtain the annotation associated with the wrapped parameter/field, if any.
*/
public Annotation getAnnotation(Class<? extends Annotation> annotationType) {
for (Annotation annotation : getAnnotations()) {
if (annotation.annotationType().equals(annotationType)) {
return annotation;
}
}
return null;
}
/** /**
* Returns true if this type is an abstract class. * Returns true if this type is an abstract class.
*/ */
...@@ -402,5 +414,5 @@ public class TypeDescriptor { ...@@ -402,5 +414,5 @@ public class TypeDescriptor {
return "[TypeDescriptor type=" + getType().getName() + "]"; return "[TypeDescriptor type=" + getType().getName() + "]";
} }
} }
} }
...@@ -13,38 +13,25 @@ ...@@ -13,38 +13,25 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format; package org.springframework.core.convert.support;
import java.text.ParseException;
import java.util.Locale;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
/** /**
* A service interface for formatting localized field values. * A generic converter that conditionally executes.
* This is the entry point into the <code>ui.format</code> system. * Often used when selectively matching custom conversion logic based on the presence of a field or class-level annotation.
* * For example, when converting from a String to a Date field, an implementation might return true if the target field has also been annotated with <code>@DateTimeFormat</code>.
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public interface FormattingService { public interface ConditionalGenericConverter extends GenericConverter {
/**
* Print the field value for display in the locale.
* @param fieldValue the field value
* @param fieldType the field type
* @param locale the user's locale
* @return the printed string
*/
String print(Object fieldValue, TypeDescriptor fieldType, Locale locale);
/** /**
* Parse the the value submitted by the user. * Should the conversion between <code>sourceFieldType</code> and <code>targetFieldType</code> be performed?
* @param submittedValue the submitted field value * @param sourceFieldType the type descriptor of the field we are converting from
* @param fieldType the field type * @param targetFieldType the type descriptor of the field we are converting to
* @param locale the user's locale * @return true if conversion should be performed, false otherwise
* @return the parsed field value
*/ */
Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException; boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
} }
\ No newline at end of file
...@@ -51,7 +51,7 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -51,7 +51,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
}; };
private final Map<Class<?>, Map<Class<?>, GenericConverter>> sourceTypeConverters = new HashMap<Class<?>, Map<Class<?>, GenericConverter>>(36); private final Map<Class<?>, Map<Class<?>, MatchableConverters>> converters = new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(36);
private ConversionService parent; private ConversionService parent;
...@@ -144,7 +144,7 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -144,7 +144,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
public void removeConvertible(Class<?> sourceType, Class<?> targetType) { public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
getSourceMap(sourceType).remove(targetType); getSourceConverterMap(sourceType).remove(targetType);
} }
// implementing ConversionService // implementing ConversionService
...@@ -183,13 +183,13 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -183,13 +183,13 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
/** /**
* Registers a GenericConverter. * Registers a GenericConverter for the source/target type pair.
* @param sourceType the source type to convert from * @param sourceType the source type to convert from
* @param targetType the target type to convert to * @param targetType the target type to convert to
* @param converter the generic converter. * @param converter the generic converter.
*/ */
public void addGenericConverter(Class<?> sourceType, Class<?> targetType, GenericConverter converter) { public void addGenericConverter(Class<?> sourceType, Class<?> targetType, GenericConverter converter) {
getSourceMap(sourceType).put(targetType, converter); getMatchableConvertersList(sourceType, targetType).add(converter);
} }
/** /**
...@@ -244,7 +244,8 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -244,7 +244,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
* @return the generic converter that will perform the conversion, or <code>null</code> if no suitable converter was found * @return the generic converter that will perform the conversion, or <code>null</code> if no suitable converter was found
*/ */
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
GenericConverter converter = findConverterByClassPair(sourceType.getObjectType(), targetType.getObjectType()); MatchableConverters matchable = findMatchableConvertersForClassPair(sourceType.getObjectType(), targetType.getObjectType());
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
if (converter != null) { if (converter != null) {
return converter; return converter;
} else if (this.parent != null && this.parent.canConvert(sourceType, targetType)) { } else if (this.parent != null && this.parent.canConvert(sourceType, targetType)) {
...@@ -273,42 +274,61 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -273,42 +274,61 @@ public class GenericConversionService implements ConversionService, ConverterReg
// internal helpers // internal helpers
private Class<?>[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
}
private MatchableConverters getMatchableConvertersList(Class<?> sourceType, Class<?> targetType) {
Map<Class<?>, MatchableConverters> sourceMap = getSourceConverterMap(sourceType);
MatchableConverters matchable = sourceMap.get(targetType);
if (matchable == null) {
matchable = new MatchableConverters();
sourceMap.put(targetType, matchable);
}
return matchable;
}
private Map<Class<?>, MatchableConverters> getSourceConverterMap(Class<?> sourceType) {
Map<Class<?>, MatchableConverters> sourceMap = converters.get(sourceType);
if (sourceMap == null) {
sourceMap = new HashMap<Class<?>, MatchableConverters>();
this.converters.put(sourceType, sourceMap);
}
return sourceMap;
}
private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) { private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(sourceType, "The sourceType to convert to is required"); Assert.notNull(sourceType, "The sourceType to convert to is required");
Assert.notNull(targetType, "The targetType to convert to is required"); Assert.notNull(targetType, "The targetType to convert to is required");
} }
private Class<?>[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) { private MatchableConverters findMatchableConvertersForClassPair(Class<?> sourceType, Class<?> targetType) {
return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
}
private GenericConverter findConverterByClassPair(Class<?> sourceType, Class<?> targetType) {
if (sourceType.isInterface()) { if (sourceType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceType); classQueue.addFirst(sourceType);
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, GenericConverter> converters = getConvertersForSource(currentClass); Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
GenericConverter converter = getConverter(converters, targetType); MatchableConverters matchable = getMatchableConvertersForTarget(converters, targetType);
if (converter != null) { if (matchable != null) {
return converter; return matchable;
} }
Class<?>[] interfaces = currentClass.getInterfaces(); Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) { for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc); classQueue.addFirst(ifc);
} }
} }
Map<Class<?>, GenericConverter> objectConverters = getConvertersForSource(Object.class); Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
return getConverter(objectConverters, targetType); return getMatchableConvertersForTarget(objectConverters, targetType);
} else { } else {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceType); classQueue.addFirst(sourceType);
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, GenericConverter> converters = getConvertersForSource(currentClass); Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
GenericConverter converter = getConverter(converters, targetType); MatchableConverters matchable = getMatchableConvertersForTarget(converters, targetType);
if (converter != null) { if (matchable != null) {
return converter; return matchable;
} }
if (currentClass.isArray()) { if (currentClass.isArray()) {
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType()); Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
...@@ -329,30 +349,22 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -329,30 +349,22 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
} }
private Map<Class<?>, GenericConverter> getSourceMap(Class<?> sourceType) { private Map<Class<?>, MatchableConverters> getTargetConvertersForSource(Class<?> sourceType) {
Map<Class<?>, GenericConverter> sourceMap = sourceTypeConverters.get(sourceType); Map<Class<?>, MatchableConverters> converters = this.converters.get(sourceType);
if (sourceMap == null) {
sourceMap = new HashMap<Class<?>, GenericConverter>();
this.sourceTypeConverters.put(sourceType, sourceMap);
}
return sourceMap;
}
private Map<Class<?>, GenericConverter> getConvertersForSource(Class<?> sourceType) {
Map<Class<?>, GenericConverter> converters = this.sourceTypeConverters.get(sourceType);
if (converters == null) { if (converters == null) {
converters = Collections.emptyMap(); converters = Collections.emptyMap();
} }
return converters; return converters;
} }
private GenericConverter getConverter(Map<Class<?>, GenericConverter> converters, Class<?> targetType) { private MatchableConverters getMatchableConvertersForTarget(Map<Class<?>, MatchableConverters> converters,
Class<?> targetType) {
if (targetType.isInterface()) { if (targetType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(targetType); classQueue.addFirst(targetType);
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
GenericConverter converter = converters.get(currentClass); MatchableConverters converter = converters.get(currentClass);
if (converter != null) { if (converter != null) {
return converter; return converter;
} }
...@@ -367,7 +379,7 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -367,7 +379,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
classQueue.addFirst(targetType); classQueue.addFirst(targetType);
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
GenericConverter converter = converters.get(currentClass); MatchableConverters converter = converters.get(currentClass);
if (converter != null) { if (converter != null) {
return converter; return converter;
} }
...@@ -390,6 +402,10 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -390,6 +402,10 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
} }
private GenericConverter matchConverter(MatchableConverters matchable, TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) {
return matchable != null ? matchable.matchConverter(sourceFieldType, targetFieldType) : null;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final class ConverterAdapter implements GenericConverter { private final class ConverterAdapter implements GenericConverter {
...@@ -424,4 +440,27 @@ public class GenericConversionService implements ConversionService, ConverterReg ...@@ -424,4 +440,27 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
} }
} private static class MatchableConverters {
private LinkedList<GenericConverter> matchableConverters = new LinkedList<GenericConverter>();
public void add(GenericConverter converter) {
this.matchableConverters.addFirst(converter);
}
public GenericConverter matchConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
for (GenericConverter matchable : this.matchableConverters) {
if (!(matchable instanceof ConditionalGenericConverter)) {
return matchable;
}
ConditionalGenericConverter conditional = (ConditionalGenericConverter) matchable;
if (conditional.matches(sourceType, targetType)) {
return matchable;
}
}
return null;
}
}
}
\ No newline at end of file
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
package org.springframework.web.bind.support; package org.springframework.web.bind.support;
import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.ui.format.FormattingService; import org.springframework.core.convert.ConversionService;
import org.springframework.validation.BindingErrorProcessor; import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
...@@ -35,7 +35,7 @@ import org.springframework.web.context.request.WebRequest; ...@@ -35,7 +35,7 @@ import org.springframework.web.context.request.WebRequest;
* @see #setMessageCodesResolver * @see #setMessageCodesResolver
* @see #setBindingErrorProcessor * @see #setBindingErrorProcessor
* @see #setValidator(Validator) * @see #setValidator(Validator)
* @see #setFormattingService(FormattingService) * @see #setConversionService(ConversionService)
* @see #setPropertyEditorRegistrar * @see #setPropertyEditorRegistrar
*/ */
public class ConfigurableWebBindingInitializer implements WebBindingInitializer { public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
...@@ -48,7 +48,7 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer ...@@ -48,7 +48,7 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
private Validator validator; private Validator validator;
private FormattingService formattingService; private ConversionService conversionService;
private PropertyEditorRegistrar[] propertyEditorRegistrars; private PropertyEditorRegistrar[] propertyEditorRegistrars;
...@@ -113,17 +113,17 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer ...@@ -113,17 +113,17 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
} }
/** /**
* Specify a FormattingService which will apply to every DataBinder. * Specify a ConversionService which will apply to every DataBinder.
*/ */
public final void setFormattingService(FormattingService formattingService) { public final void setConversionService(ConversionService conversionService) {
this.formattingService = formattingService; this.conversionService = conversionService;
} }
/** /**
* Return the FormattingService which will apply to every DataBinder. * Return the ConversionService which will apply to every DataBinder.
*/ */
public final FormattingService getFormattingService() { public final ConversionService getConversionService() {
return this.formattingService; return this.conversionService;
} }
/** /**
...@@ -162,8 +162,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer ...@@ -162,8 +162,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
this.validator.supports(binder.getTarget().getClass())) { this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator); binder.setValidator(this.validator);
} }
if (this.formattingService != null) { if (this.conversionService != null) {
binder.setFormattingService(this.formattingService); binder.setConversionService(this.conversionService);
} }
if (this.propertyEditorRegistrars != null) { if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册