formatters now plugged into unified type conversion api with formatter specific registry spi
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@2248 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
254f477423
commit
c8d2353322
|
|
@ -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> {
|
||||||
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 <T> the printer accepts is not assignable to <code>fieldType</code>, a coersion from <code>fieldType</code> to <T> 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 @@
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service interface for formatting localized field values.
|
|
||||||
* This is the entry point into the <code>ui.format</code> system.
|
|
||||||
*
|
|
||||||
* @author Keith Donald
|
|
||||||
* @since 3.0
|
|
||||||
*/
|
|
||||||
public interface FormattingService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @param submittedValue the submitted field value
|
|
||||||
* @param fieldType the field type
|
|
||||||
* @param locale the user's locale
|
|
||||||
* @return the parsed field value
|
|
||||||
*/
|
|
||||||
Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
// 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;
|
||||||
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 {
|
||||||
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 {
|
||||||
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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
/*
|
||||||
|
* 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.lang.annotation.Annotation;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.core.GenericTypeResolver;
|
||||||
|
import org.springframework.core.convert.ConversionFailedException;
|
||||||
|
import org.springframework.core.convert.ConversionService;
|
||||||
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
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.GenericConverter;
|
||||||
|
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||||
|
import org.springframework.ui.format.Formatter;
|
||||||
|
import org.springframework.ui.format.FormatterRegistry;
|
||||||
|
import org.springframework.ui.format.Parser;
|
||||||
|
import org.springframework.ui.format.Printer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ConversionService implementation designed to be configured as a {@link FormatterRegistry}..
|
||||||
|
* @author Keith Donald
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
public class FormattingConversionService implements FormatterRegistry, ConversionService {
|
||||||
|
|
||||||
|
private GenericConversionService conversionService = new GenericConversionService();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 FormattingConversionService() {
|
||||||
|
this.conversionService.setParent(new DefaultConversionService());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new FormattingConversionService, initially with no Formatters registered.
|
||||||
|
* The conversion logic contained in the parent is merged with this service.
|
||||||
|
*/
|
||||||
|
public FormattingConversionService(ConversionService parent) {
|
||||||
|
this.conversionService.setParent(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// implementing FormattingRegistry
|
||||||
|
|
||||||
|
public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
|
||||||
|
this.conversionService.addGenericConverter(fieldType, String.class, new PrinterConverter(printer, this.conversionService));
|
||||||
|
this.conversionService.addGenericConverter(String.class, fieldType, new ParserConverter(parser, this.conversionService));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
|
||||||
|
this.conversionService.addGenericConverter(fieldType, String.class, new PrinterConverter(formatter, this.conversionService));
|
||||||
|
this.conversionService.addGenericConverter(String.class, fieldType, new ParserConverter(formatter, this.conversionService));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void addFormatterForFieldAnnotation(final AnnotationFormatterFactory annotationFormatterFactory) {
|
||||||
|
final Class<? extends Annotation> annotationType = resolveAnnotationType(annotationFormatterFactory);
|
||||||
|
if (annotationType == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory ["
|
||||||
|
+ annotationFormatterFactory.getClass().getName()
|
||||||
|
+ "]; 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConverterRegistry getConverterRegistry() {
|
||||||
|
return this.conversionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// implementing ConverisonService
|
||||||
|
|
||||||
|
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
|
||||||
|
return canConvert(TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T convert(Object source, Class<T> targetType) {
|
||||||
|
return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||||
|
return this.conversionService.canConvert(sourceType, targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||||
|
return this.conversionService.convert(source, sourceType, targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Class<? extends Annotation> resolveAnnotationType(AnnotationFormatterFactory<?> annotationFormatterFactory) {
|
||||||
|
return (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrinterConverter implements GenericConverter {
|
||||||
|
|
||||||
|
private TypeDescriptor printerObjectType;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Printer printer;
|
||||||
|
|
||||||
|
private ConversionService conversionService;
|
||||||
|
|
||||||
|
public PrinterConverter(Printer<?> printer, ConversionService conversionService) {
|
||||||
|
this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
|
||||||
|
this.printer = printer;
|
||||||
|
this.conversionService = conversionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||||
|
if (!sourceType.isAssignableTo(this.printerObjectType)) {
|
||||||
|
source = this.conversionService.convert(source, sourceType, this.printerObjectType);
|
||||||
|
}
|
||||||
|
return source != null ? this.printer.print(source, LocaleContextHolder.getLocale()) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> resolvePrinterObjectType(Printer<?> printer) {
|
||||||
|
return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ParserConverter implements GenericConverter {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Parser parser;
|
||||||
|
|
||||||
|
private ConversionService conversionService;
|
||||||
|
|
||||||
|
public ParserConverter(Parser<?> parser, ConversionService conversionService) {
|
||||||
|
this.parser = parser;
|
||||||
|
this.conversionService = conversionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||||
|
String submittedValue = (String) source;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 @@
|
||||||
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;
|
||||||
*/
|
*/
|
||||||
public class FormattingPropertyEditorAdapter extends PropertyEditorSupport {
|
public class FormattingPropertyEditorAdapter extends PropertyEditorSupport {
|
||||||
|
|
||||||
private final FormattingService formattingService;
|
private final ConversionService conversionService;
|
||||||
|
|
||||||
private final TypeDescriptor fieldType;
|
private final Class<?> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.lang.annotation.Annotation;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import org.springframework.core.GenericTypeResolver;
|
|
||||||
import org.springframework.core.convert.ConversionService;
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
|
||||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
|
||||||
import org.springframework.core.convert.support.GenericConversionService;
|
|
||||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
|
||||||
import org.springframework.ui.format.Formatter;
|
|
||||||
import org.springframework.ui.format.FormatterRegistry;
|
|
||||||
import org.springframework.ui.format.FormattingService;
|
|
||||||
import org.springframework.ui.format.Parser;
|
|
||||||
import org.springframework.ui.format.Printer;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic implementation of {@link FormattingService} suitable for use in most environments.
|
|
||||||
* Is a {@link FormatterRegistry} to allow for registration of field formatting logic.
|
|
||||||
*
|
|
||||||
* @author Keith Donald
|
|
||||||
* @author Juergen Hoeller
|
|
||||||
* @since 3.0
|
|
||||||
*/
|
|
||||||
public class GenericFormattingService implements FormattingService, FormatterRegistry {
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a parent of the type conversion service that will be used to coerce objects to types required for formatting.
|
|
||||||
*/
|
|
||||||
public void setParentConversionService(ConversionService parentConversionService) {
|
|
||||||
this.conversionService.setParent(parentConversionService);
|
|
||||||
}
|
|
||||||
|
|
||||||
// implementing FormattingService
|
|
||||||
|
|
||||||
public String print(Object fieldValue, TypeDescriptor fieldType, Locale locale) {
|
|
||||||
return getFormatter(fieldType).print(fieldValue, fieldType, locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException {
|
|
||||||
return getFormatter(fieldType).parse(submittedValue, fieldType, locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
// implementing FormatterRegistry
|
|
||||||
|
|
||||||
public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
|
|
||||||
Class<?> printerObjectType = resolvePrinterObjectType(printer);
|
|
||||||
Class<?> parserObjectType = resolveParserObjectType(parser);
|
|
||||||
this.typeFormatters.put(fieldType, new GenericFormatter(printerObjectType, printer, parserObjectType, parser));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
|
|
||||||
Class<?> formatterObjectType = resolveFormatterObjectType(formatter);
|
|
||||||
this.typeFormatters.put(fieldType, new GenericFormatter(formatterObjectType, formatter, formatterObjectType, formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<?> annotationFormatterFactory) {
|
|
||||||
Class<? extends Annotation> annotationType = resolveAnnotationType(annotationFormatterFactory);
|
|
||||||
if (annotationType == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory ["
|
|
||||||
+ annotationFormatterFactory.getClass().getName()
|
|
||||||
+ "]; does the factory parameterize the <A extends Annotation> generic type?");
|
|
||||||
}
|
|
||||||
this.annotationFormatters.put(annotationType, new GenericAnnotationFormatterFactory(annotationFormatterFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConverterRegistry getConverterRegistry() {
|
|
||||||
return this.conversionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal helpers
|
|
||||||
|
|
||||||
private Class<?> resolveParserObjectType(Parser<?> parser) {
|
|
||||||
return GenericTypeResolver.resolveTypeArgument(parser.getClass(), Parser.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> resolvePrinterObjectType(Printer<?> printer) {
|
|
||||||
return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> resolveFormatterObjectType(Formatter<?> formatter) {
|
|
||||||
return GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Class<? extends Annotation> resolveAnnotationType(AnnotationFormatterFactory<?> annotationFormatterFactory) {
|
|
||||||
return (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GenericFormatter getFormatter(TypeDescriptor fieldType) {
|
|
||||||
Assert.notNull(fieldType, "Field TypeDescriptor is required");
|
|
||||||
GenericFormatter formatter = findFormatterForAnnotatedField(fieldType);
|
|
||||||
Class<?> fieldObjectType = fieldType.getObjectType();
|
|
||||||
if (formatter == null) {
|
|
||||||
formatter = findFormatterForFieldType(fieldObjectType);
|
|
||||||
}
|
|
||||||
return formatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GenericFormatter findFormatterForAnnotatedField(TypeDescriptor fieldType) {
|
|
||||||
for (Annotation annotation : fieldType.getAnnotations()) {
|
|
||||||
GenericFormatter formatter = findFormatterForAnnotation(annotation, fieldType.getObjectType());
|
|
||||||
if (formatter != null) {
|
|
||||||
return formatter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GenericFormatter findFormatterForAnnotation(Annotation annotation, Class<?> fieldType) {
|
|
||||||
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) {
|
|
||||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
|
||||||
classQueue.addFirst(fieldType);
|
|
||||||
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 class GenericFormatter {
|
|
||||||
|
|
||||||
private TypeDescriptor printerObjectType;
|
|
||||||
|
|
||||||
private Printer printer;
|
|
||||||
|
|
||||||
private Parser parser;
|
|
||||||
|
|
||||||
public GenericFormatter(Class<?> printerObjectType, Printer<?> printer, Class<?> parserObjectType, Parser<?> parser) {
|
|
||||||
this.printerObjectType = TypeDescriptor.valueOf(printerObjectType);
|
|
||||||
this.printer = printer;
|
|
||||||
this.parser = parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if (submittedValue.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private class GenericAnnotationFormatterFactory {
|
|
||||||
|
|
||||||
private AnnotationFormatterFactory annotationFormatterFactory;
|
|
||||||
|
|
||||||
public GenericAnnotationFormatterFactory(AnnotationFormatterFactory<?> annotationFormatterFactory) {
|
|
||||||
this.annotationFormatterFactory = annotationFormatterFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GenericFormatter getFormatter(Annotation annotation, Class<?> fieldType) {
|
|
||||||
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
|
|
||||||
|
|
||||||
private Class<?> getPrinterObjectType(Printer<?> printer, Class<?> fieldType) {
|
|
||||||
// TODO cache
|
|
||||||
return resolvePrinterObjectType(printer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> getParserObjectType(Parser<?> parser, Class<?> fieldType) {
|
|
||||||
// TODO cache
|
|
||||||
return resolveParserObjectType(parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
*/
|
*/
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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;
|
||||||
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 {
|
||||||
|
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
private FormattingService formattingService;
|
private ConversionService conversionService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
});
|
});
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,9 +93,11 @@ public class GenericFormattingServiceTests {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,5 +109,4 @@ public class GenericFormattingServiceTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
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 {
|
||||||
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;
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.core.convert.support;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic converter that conditionally executes.
|
||||||
|
* 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
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
public interface ConditionalGenericConverter extends GenericConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the conversion between <code>sourceFieldType</code> and <code>targetFieldType</code> be performed?
|
||||||
|
* @param sourceFieldType the type descriptor of the field we are converting from
|
||||||
|
* @param targetFieldType the type descriptor of the field we are converting to
|
||||||
|
* @return true if conversion should be performed, false otherwise
|
||||||
|
*/
|
||||||
|
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* @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
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
* @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
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue