diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java index c27f33fcbf7..6c87d9741b4 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java @@ -19,14 +19,14 @@ import java.lang.annotation.Annotation; 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}. * *

For example, a DateTimeFormatAnnotationFormatterFactory might create a formatter - * that formats a Date objects set on properties annotated with @DateFormat. + * that formats Date values set on fields annotated with @DateTimeFormat. * * @author Keith Donald * @since 3.0 - * @param the type of Annotation that should trigger property formatting + * @param the annotation type that should trigger formatting */ public interface AnnotationFormatterFactory { @@ -36,17 +36,19 @@ public interface AnnotationFormatterFactory { Set> getFieldTypes(); /** - * Get the Printer to print the value of a property of fieldType annotated with annotation. + * Get the Printer to print the value of a field of fieldType annotated with annotation. + * If the type <T> the printer accepts is not assignable to fieldType, a coersion from fieldType to <T> will be attempted before the Printer is invoked. * @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 */ Printer getPrinter(A annotation, Class fieldType); /** - * Get the Parser to parse the printed value of a property of fieldType annotated with annotation. + * Get the Parser to parse a submitted value for a field of fieldType annotated with annotation. + * If the object the parser returns is not assignable to fieldType, a coersion to fieldType will be attempted before the field is set. * @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 */ Parser getParser(A annotation, Class fieldType); diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java index 38864d1de91..33a82ce67cc 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java @@ -15,6 +15,8 @@ */ package org.springframework.ui.format; +import java.lang.annotation.Annotation; + 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. * @param annotationFormatterFactory the annotation formatter factory to add */ - void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory); + void addFormatterForFieldAnnotation(AnnotationFormatterFactory annotationFormatterFactory); /** * Returns the registry of Converters that coerse field values to types required by Formatters. - * Allows clients to register their own custom converters. - * For example, a date/time formatting configuration might expect a java.util.Date field value to be converted to a Long for formatting. + * 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 coersed to a Long for formatting. * 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 */ diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/FormattingService.java b/org.springframework.context/src/main/java/org/springframework/ui/format/FormattingService.java deleted file mode 100644 index 91f58c1d3d9..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/FormattingService.java +++ /dev/null @@ -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 ui.format 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; - -} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/AbstractDateTimeAnnotationFormatterFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/AbstractDateTimeAnnotationFormatterFactory.java index 65c2bca7254..6c48799f408 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/AbstractDateTimeAnnotationFormatterFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/AbstractDateTimeAnnotationFormatterFactory.java @@ -41,7 +41,7 @@ abstract class AbstractDateTimeAnnotationFormatterFactory private final Set> propertyTypes; public AbstractDateTimeAnnotationFormatterFactory() { - this.propertyTypes = Collections.unmodifiableSet(createPropertyTypes()); + this.propertyTypes = Collections.unmodifiableSet(createFieldTypes()); } public Set> getFieldTypes() { @@ -76,7 +76,7 @@ abstract class AbstractDateTimeAnnotationFormatterFactory // internal helpers - private Set> createPropertyTypes() { + private Set> createFieldTypes() { Set> propertyTypes = new HashSet>(5); propertyTypes.add(ReadableInstant.class); propertyTypes.add(ReadablePartial.class); diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeConverters.java b/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeConverters.java index 538da9b710b..9cb55ae822c 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeConverters.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeConverters.java @@ -28,7 +28,7 @@ import org.springframework.core.convert.converter.Converter; 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 */ final class JodaTimeConverters { @@ -49,62 +49,88 @@ final class JodaTimeConverters { registry.addConverter(new DateTimeToDateConverter()); registry.addConverter(new DateTimeToCalendarConverter()); registry.addConverter(new DateToLongConverter()); - registry.addConverter(new CalendarToDateTimeConverter()); + registry.addConverter(new CalendarToReadableInstantConverter()); } // 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 { public LocalDate convert(DateTime source) { 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 { public LocalTime convert(DateTime source) { 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 { public LocalDateTime convert(DateTime source) { 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 { public DateMidnight convert(DateTime source) { 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 { public Date convert(DateTime source) { 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 { public Calendar convert(DateTime source) { 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 { public Long convert(Date source) { return source.getTime(); } } - // used when formatting a java.util.Calendar property with a ReadableInstantPrinter - private static class CalendarToDateTimeConverter implements Converter { + /** + * Used when printing a java.util.Calendar field with a ReadableInstantPrinter. + * @see MillisecondInstantPrinter + * @see DateTimeFormatAnnotationFormatterFactory + */ + private static class CalendarToReadableInstantConverter implements Converter { public ReadableInstant convert(Calendar source) { return new DateTime(source); } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeFormattingConfigurer.java b/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeFormattingConfigurer.java index 36fdd2af158..31415f8d525 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeFormattingConfigurer.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/jodatime/JodaTimeFormattingConfigurer.java @@ -111,7 +111,7 @@ public class JodaTimeFormattingConfigurer { Printer readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter); this.formatterRegistry.addFormatterForFieldType(ReadableInstant.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()); } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionService.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionService.java new file mode 100644 index 00000000000..b2dd8c8b7cb --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionService.java @@ -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 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 generic type?"); + } + Set> 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 convert(Object source, Class 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 resolveAnnotationType(AnnotationFormatterFactory annotationFormatterFactory) { + return (Class) 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; + } + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java deleted file mode 100644 index a3360cb4cc7..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java +++ /dev/null @@ -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()); - } - }); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java index 1c02ed0e85e..4f4e10d5ccc 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java @@ -17,11 +17,8 @@ package org.springframework.ui.format.support; import java.beans.PropertyEditorSupport; -import java.text.ParseException; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.ui.format.FormattingService; +import org.springframework.core.convert.ConversionService; import org.springframework.util.Assert; /** @@ -34,35 +31,29 @@ import org.springframework.util.Assert; */ 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. * @param formatter the Formatter to wrap */ - public FormattingPropertyEditorAdapter(FormattingService formattingService, Class fieldType) { - Assert.notNull(formattingService, "FormattingService must not be null"); + public FormattingPropertyEditorAdapter(ConversionService formattingService, Class fieldType) { + Assert.notNull(formattingService, "ConversionService must not be null"); Assert.notNull(formattingService, "FieldType must not be null"); - this.formattingService = formattingService; - this.fieldType = TypeDescriptor.valueOf(fieldType); + this.conversionService = formattingService; + this.fieldType = fieldType; } - @Override public void setAsText(String text) throws IllegalArgumentException { - try { - setValue(this.formattingService.parse(text, this.fieldType, LocaleContextHolder.getLocale())); - } - catch (ParseException ex) { - throw new IllegalArgumentException("Failed to parse formatted value", ex); - } + setValue(this.conversionService.convert(text, this.fieldType)); } @Override public String getAsText() { - return this.formattingService.print(getValue(), this.fieldType, LocaleContextHolder.getLocale()); + return this.conversionService.convert(getValue(), String.class); } } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormattingService.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormattingService.java deleted file mode 100644 index 6bb5bcdcf8c..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormattingService.java +++ /dev/null @@ -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, GenericFormatter> typeFormatters = new ConcurrentHashMap, GenericFormatter>(); - - private final Map, GenericAnnotationFormatterFactory> annotationFormatters = new ConcurrentHashMap, 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 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 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 resolveAnnotationType(AnnotationFormatterFactory annotationFormatterFactory) { - return (Class) 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 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> classQueue = new LinkedList>(); - 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); - } - - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java index 8fdd8aa81a3..fa9f46f66d6 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java @@ -22,10 +22,8 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.PropertyAccessorUtils; 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.ui.format.FormattingService; -import org.springframework.ui.format.support.FormattingConversionServiceAdapter; import org.springframework.ui.format.support.FormattingPropertyEditorAdapter; import org.springframework.util.Assert; @@ -43,7 +41,7 @@ import org.springframework.util.Assert; */ 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) { - Assert.notNull(formattingService, "FormattingService must not be null"); - this.formattingService = formattingService; - getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formattingService)); + public void initConversion(ConversionService conversionService) { + Assert.notNull(conversionService, "ConversionService must not be null"); + this.conversionService = conversionService; + getPropertyAccessor().setConversionService(conversionService); } /** @@ -116,10 +114,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul return textValue; } } - if (this.formattingService != null) { + if (this.conversionService != null) { // Try custom formatter... TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField); - return this.formattingService.print(value, td, LocaleContextHolder.getLocale()); + return this.conversionService.convert(value, td, TypeDescriptor.valueOf(String.class)); } else { return value; } @@ -149,11 +147,11 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul valueType = getFieldType(field); } PropertyEditor editor = super.findEditor(field, valueType); - if (editor == null && this.formattingService != null) { + if (editor == null && this.conversionService != null) { TypeDescriptor td = (field != null ? getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)) : TypeDescriptor.valueOf(valueType)); - editor = new FormattingPropertyEditorAdapter(this.formattingService, valueType); + editor = new FormattingPropertyEditorAdapter(this.conversionService, valueType); } return editor; } diff --git a/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java b/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java index 73f1128f69c..0973e8a3f58 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java @@ -34,8 +34,7 @@ import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; import org.springframework.core.MethodParameter; -import org.springframework.ui.format.FormattingService; -import org.springframework.ui.format.support.FormattingConversionServiceAdapter; +import org.springframework.core.convert.ConversionService; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; @@ -135,7 +134,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { private Validator validator; - private FormattingService formattingService; + private ConversionService conversionService; /** @@ -183,8 +182,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { Assert.isNull(this.bindingResult, "DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods"); this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName()); - if (this.formattingService != null) { - this.bindingResult.initFormatting(this.formattingService); + if (this.conversionService != null) { + this.bindingResult.initConversion(this.conversionService); } } @@ -197,8 +196,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { Assert.isNull(this.bindingResult, "DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods"); this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName()); - if (this.formattingService != null) { - this.bindingResult.initFormatting(this.formattingService); + if (this.conversionService != null) { + this.bindingResult.initConversion(this.conversionService); } } @@ -226,8 +225,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { protected SimpleTypeConverter getSimpleTypeConverter() { if (this.typeConverter == null) { this.typeConverter = new SimpleTypeConverter(); - if (this.formattingService != null) { - this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formattingService)); + if (this.conversionService != null) { + this.typeConverter.setConversionService(this.conversionService); } } 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) { - this.formattingService = formattingService; + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; } //--------------------------------------------------------------------- @@ -492,7 +491,6 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { public T convertIfNecessary( Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException { - return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); } diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormattingServiceTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormattingServiceTests.java index e76519a4bea..a3e65c93c67 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormattingServiceTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormattingServiceTests.java @@ -25,11 +25,12 @@ import java.util.Locale; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.TypeDescriptor; 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.DateTimeParser; import org.springframework.ui.format.jodatime.ReadablePartialPrinter; @@ -42,20 +43,25 @@ import org.springframework.ui.format.number.IntegerFormatter; */ public class GenericFormattingServiceTests { - private GenericFormattingService formattingService; + private FormattingConversionService formattingService; @Before public void setUp() { - formattingService = new GenericFormattingService(); - formattingService.setParentConversionService(new DefaultConversionService()); + formattingService = new FormattingConversionService(); + LocaleContextHolder.setLocale(Locale.US); + } + + @After + public void tearDown() { + LocaleContextHolder.setLocale(null); } @Test public void testFormatFieldForTypeWithFormatter() throws ParseException { 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); - 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); } @@ -68,9 +74,9 @@ public class GenericFormattingServiceTests { }); formattingService.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(DateTimeFormat .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); - 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); } @@ -85,21 +91,22 @@ public class GenericFormattingServiceTests { public Date convert(DateTime source) { return source.toDate(); } - }); + }); 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); - 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); } - + private static class Model { - + @SuppressWarnings("unused") - @org.springframework.ui.format.jodatime.DateTimeFormat(dateStyle=FormatStyle.SHORT) + @org.springframework.ui.format.jodatime.DateTimeFormat(dateStyle = FormatStyle.SHORT) public Date date; - + } - } diff --git a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java index 199967f5b27..567c0524444 100644 --- a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -22,10 +22,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; 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.Map; import java.util.Set; @@ -49,7 +45,7 @@ import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.context.support.StaticMessageSource; import org.springframework.core.convert.support.DefaultConversionService; 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; /** @@ -303,10 +299,9 @@ public class DataBinderTests extends TestCase { public void testBindingWithFormatter() { TestBean tb = new TestBean(); DataBinder binder = new DataBinder(tb); - GenericFormattingService formattingService = new GenericFormattingService(); - formattingService.setParentConversionService(new DefaultConversionService()); - formattingService.addFormatterForFieldType(Float.class, new DecimalFormatter()); - binder.setFormattingService(formattingService); + FormattingConversionService conversionService = new FormattingConversionService(); + conversionService.addFormatterForFieldType(Float.class, new DecimalFormatter()); + binder.setConversionService(conversionService); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.addPropertyValue("myFloat", "1,2"); diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java index c62cdcda330..ff52b3af437 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ConversionService.java @@ -26,6 +26,7 @@ package org.springframework.core.convert; public interface ConversionService { /** + * TODO - do we really need to support this? * Returns true if objects of sourceType can be converted to targetType. * @param sourceType the source type to convert from (required) * @param targetType the target type to convert to (required) @@ -34,6 +35,7 @@ public interface ConversionService { boolean canConvert(Class sourceType, Class targetType); /** + * TODO - do we really need to support this? * Convert the source to targetType. * @param source the source object to convert (may be null) * @param targetType the target type to convert to (required) diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index d6d086f0b5d..8b51b047993 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -283,6 +283,18 @@ public class TypeDescriptor { } } + /** + * Obtain the annotation associated with the wrapped parameter/field, if any. + */ + public Annotation getAnnotation(Class annotationType) { + for (Annotation annotation : getAnnotations()) { + if (annotation.annotationType().equals(annotationType)) { + return annotation; + } + } + return null; + } + /** * Returns true if this type is an abstract class. */ @@ -402,5 +414,5 @@ public class TypeDescriptor { return "[TypeDescriptor type=" + getType().getName() + "]"; } } - + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConditionalGenericConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConditionalGenericConverter.java new file mode 100644 index 00000000000..717a9f26e17 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConditionalGenericConverter.java @@ -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 @DateTimeFormat. + * @author Keith Donald + * @since 3.0 + */ +public interface ConditionalGenericConverter extends GenericConverter { + + /** + * Should the conversion between sourceFieldType and targetFieldType 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); + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index 09953aa0035..2b6e1fefcd6 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -51,7 +51,7 @@ public class GenericConversionService implements ConversionService, ConverterReg } }; - private final Map, Map, GenericConverter>> sourceTypeConverters = new HashMap, Map, GenericConverter>>(36); + private final Map, Map, MatchableConverters>> converters = new HashMap, Map, MatchableConverters>>(36); private ConversionService parent; @@ -144,7 +144,7 @@ public class GenericConversionService implements ConversionService, ConverterReg } public void removeConvertible(Class sourceType, Class targetType) { - getSourceMap(sourceType).remove(targetType); + getSourceConverterMap(sourceType).remove(targetType); } // 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 targetType the target type to convert to * @param converter the generic 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 null if no suitable converter was found */ 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) { return converter; } else if (this.parent != null && this.parent.canConvert(sourceType, targetType)) { @@ -273,42 +274,61 @@ public class GenericConversionService implements ConversionService, ConverterReg // internal helpers + private Class[] getRequiredTypeInfo(Object converter, Class genericIfc) { + return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc); + } + + private MatchableConverters getMatchableConvertersList(Class sourceType, Class targetType) { + Map, MatchableConverters> sourceMap = getSourceConverterMap(sourceType); + MatchableConverters matchable = sourceMap.get(targetType); + if (matchable == null) { + matchable = new MatchableConverters(); + sourceMap.put(targetType, matchable); + } + return matchable; + } + + private Map, MatchableConverters> getSourceConverterMap(Class sourceType) { + Map, MatchableConverters> sourceMap = converters.get(sourceType); + if (sourceMap == null) { + sourceMap = new HashMap, MatchableConverters>(); + this.converters.put(sourceType, sourceMap); + } + return sourceMap; + } + private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(sourceType, "The sourceType to convert to is required"); Assert.notNull(targetType, "The targetType to convert to is required"); } - private Class[] getRequiredTypeInfo(Object converter, Class genericIfc) { - return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc); - } - - private GenericConverter findConverterByClassPair(Class sourceType, Class targetType) { + private MatchableConverters findMatchableConvertersForClassPair(Class sourceType, Class targetType) { if (sourceType.isInterface()) { LinkedList> classQueue = new LinkedList>(); classQueue.addFirst(sourceType); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); - Map, GenericConverter> converters = getConvertersForSource(currentClass); - GenericConverter converter = getConverter(converters, targetType); - if (converter != null) { - return converter; + Map, MatchableConverters> converters = getTargetConvertersForSource(currentClass); + MatchableConverters matchable = getMatchableConvertersForTarget(converters, targetType); + if (matchable != null) { + return matchable; } Class[] interfaces = currentClass.getInterfaces(); for (Class ifc : interfaces) { classQueue.addFirst(ifc); } } - Map, GenericConverter> objectConverters = getConvertersForSource(Object.class); - return getConverter(objectConverters, targetType); + Map, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class); + return getMatchableConvertersForTarget(objectConverters, targetType); } else { LinkedList> classQueue = new LinkedList>(); classQueue.addFirst(sourceType); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); - Map, GenericConverter> converters = getConvertersForSource(currentClass); - GenericConverter converter = getConverter(converters, targetType); - if (converter != null) { - return converter; + Map, MatchableConverters> converters = getTargetConvertersForSource(currentClass); + MatchableConverters matchable = getMatchableConvertersForTarget(converters, targetType); + if (matchable != null) { + return matchable; } if (currentClass.isArray()) { Class componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType()); @@ -329,30 +349,22 @@ public class GenericConversionService implements ConversionService, ConverterReg } } - private Map, GenericConverter> getSourceMap(Class sourceType) { - Map, GenericConverter> sourceMap = sourceTypeConverters.get(sourceType); - if (sourceMap == null) { - sourceMap = new HashMap, GenericConverter>(); - this.sourceTypeConverters.put(sourceType, sourceMap); - } - return sourceMap; - } - - private Map, GenericConverter> getConvertersForSource(Class sourceType) { - Map, GenericConverter> converters = this.sourceTypeConverters.get(sourceType); + private Map, MatchableConverters> getTargetConvertersForSource(Class sourceType) { + Map, MatchableConverters> converters = this.converters.get(sourceType); if (converters == null) { converters = Collections.emptyMap(); } return converters; } - private GenericConverter getConverter(Map, GenericConverter> converters, Class targetType) { + private MatchableConverters getMatchableConvertersForTarget(Map, MatchableConverters> converters, + Class targetType) { if (targetType.isInterface()) { LinkedList> classQueue = new LinkedList>(); classQueue.addFirst(targetType); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); - GenericConverter converter = converters.get(currentClass); + MatchableConverters converter = converters.get(currentClass); if (converter != null) { return converter; } @@ -367,7 +379,7 @@ public class GenericConversionService implements ConversionService, ConverterReg classQueue.addFirst(targetType); while (!classQueue.isEmpty()) { Class currentClass = classQueue.removeLast(); - GenericConverter converter = converters.get(currentClass); + MatchableConverters converter = converters.get(currentClass); if (converter != null) { 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") private final class ConverterAdapter implements GenericConverter { @@ -424,4 +440,27 @@ public class GenericConversionService implements ConversionService, ConverterReg } } -} + private static class MatchableConverters { + + private LinkedList matchableConverters = new LinkedList(); + + 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 diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java index 73a0b578dff..538f4c22e70 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java @@ -17,7 +17,7 @@ package org.springframework.web.bind.support; 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.MessageCodesResolver; import org.springframework.validation.Validator; @@ -35,7 +35,7 @@ import org.springframework.web.context.request.WebRequest; * @see #setMessageCodesResolver * @see #setBindingErrorProcessor * @see #setValidator(Validator) - * @see #setFormattingService(FormattingService) + * @see #setConversionService(ConversionService) * @see #setPropertyEditorRegistrar */ public class ConfigurableWebBindingInitializer implements WebBindingInitializer { @@ -48,7 +48,7 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer private Validator validator; - private FormattingService formattingService; + private ConversionService conversionService; 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) { - this.formattingService = formattingService; + public final void setConversionService(ConversionService conversionService) { + 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() { - return this.formattingService; + public final ConversionService getConversionService() { + return this.conversionService; } /** @@ -162,8 +162,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer this.validator.supports(binder.getTarget().getClass())) { binder.setValidator(this.validator); } - if (this.formattingService != null) { - binder.setFormattingService(this.formattingService); + if (this.conversionService != null) { + binder.setConversionService(this.conversionService); } if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {