001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.format.support;
018
019import java.lang.annotation.Annotation;
020import java.util.Collections;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.springframework.context.EmbeddedValueResolverAware;
026import org.springframework.context.i18n.LocaleContextHolder;
027import org.springframework.core.DecoratingProxy;
028import org.springframework.core.GenericTypeResolver;
029import org.springframework.core.convert.ConversionService;
030import org.springframework.core.convert.TypeDescriptor;
031import org.springframework.core.convert.converter.ConditionalGenericConverter;
032import org.springframework.core.convert.converter.GenericConverter;
033import org.springframework.core.convert.support.GenericConversionService;
034import org.springframework.format.AnnotationFormatterFactory;
035import org.springframework.format.Formatter;
036import org.springframework.format.FormatterRegistry;
037import org.springframework.format.Parser;
038import org.springframework.format.Printer;
039import org.springframework.util.StringUtils;
040import org.springframework.util.StringValueResolver;
041
042/**
043 * A {@link org.springframework.core.convert.ConversionService} implementation
044 * designed to be configured as a {@link FormatterRegistry}.
045 *
046 * @author Keith Donald
047 * @author Juergen Hoeller
048 * @since 3.0
049 */
050public class FormattingConversionService extends GenericConversionService
051                implements FormatterRegistry, EmbeddedValueResolverAware {
052
053        private StringValueResolver embeddedValueResolver;
054
055        private final Map<AnnotationConverterKey, GenericConverter> cachedPrinters =
056                        new ConcurrentHashMap<AnnotationConverterKey, GenericConverter>(64);
057
058        private final Map<AnnotationConverterKey, GenericConverter> cachedParsers =
059                        new ConcurrentHashMap<AnnotationConverterKey, GenericConverter>(64);
060
061
062        @Override
063        public void setEmbeddedValueResolver(StringValueResolver resolver) {
064                this.embeddedValueResolver = resolver;
065        }
066
067
068        @Override
069        public void addFormatter(Formatter<?> formatter) {
070                addFormatterForFieldType(getFieldType(formatter), formatter);
071        }
072
073        @Override
074        public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
075                addConverter(new PrinterConverter(fieldType, formatter, this));
076                addConverter(new ParserConverter(fieldType, formatter, this));
077        }
078
079        @Override
080        public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
081                addConverter(new PrinterConverter(fieldType, printer, this));
082                addConverter(new ParserConverter(fieldType, parser, this));
083        }
084
085        @Override
086        public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
087                Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
088                if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
089                        ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
090                }
091                Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
092                for (Class<?> fieldType : fieldTypes) {
093                        addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
094                        addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
095                }
096        }
097
098
099        static Class<?> getFieldType(Formatter<?> formatter) {
100                Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
101                if (fieldType == null && formatter instanceof DecoratingProxy) {
102                        fieldType = GenericTypeResolver.resolveTypeArgument(
103                                        ((DecoratingProxy) formatter).getDecoratedClass(), Formatter.class);
104                }
105                if (fieldType == null) {
106                        throw new IllegalArgumentException("Unable to extract the parameterized field type from Formatter [" +
107                                        formatter.getClass().getName() + "]; does the class parameterize the <T> generic type?");
108                }
109                return fieldType;
110        }
111
112        @SuppressWarnings("unchecked")
113        static Class<? extends Annotation> getAnnotationType(AnnotationFormatterFactory<? extends Annotation> factory) {
114                Class<? extends Annotation> annotationType = (Class<? extends Annotation>)
115                                GenericTypeResolver.resolveTypeArgument(factory.getClass(), AnnotationFormatterFactory.class);
116                if (annotationType == null) {
117                        throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from " +
118                                        "AnnotationFormatterFactory [" + factory.getClass().getName() +
119                                        "]; does the factory parameterize the <A extends Annotation> generic type?");
120                }
121                return annotationType;
122        }
123
124
125        private static class PrinterConverter implements GenericConverter {
126
127                private final Class<?> fieldType;
128
129                private final TypeDescriptor printerObjectType;
130
131                @SuppressWarnings("rawtypes")
132                private final Printer printer;
133
134                private final ConversionService conversionService;
135
136                public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) {
137                        this.fieldType = fieldType;
138                        this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
139                        this.printer = printer;
140                        this.conversionService = conversionService;
141                }
142
143                @Override
144                public Set<ConvertiblePair> getConvertibleTypes() {
145                        return Collections.singleton(new ConvertiblePair(this.fieldType, String.class));
146                }
147
148                @Override
149                @SuppressWarnings("unchecked")
150                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
151                        if (source == null) {
152                                return "";
153                        }
154                        if (!sourceType.isAssignableTo(this.printerObjectType)) {
155                                source = this.conversionService.convert(source, sourceType, this.printerObjectType);
156                        }
157                        return this.printer.print(source, LocaleContextHolder.getLocale());
158                }
159
160                private Class<?> resolvePrinterObjectType(Printer<?> printer) {
161                        return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
162                }
163
164                @Override
165                public String toString() {
166                        return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer);
167                }
168        }
169
170
171        private static class ParserConverter implements GenericConverter {
172
173                private final Class<?> fieldType;
174
175                private final Parser<?> parser;
176
177                private final ConversionService conversionService;
178
179                public ParserConverter(Class<?> fieldType, Parser<?> parser, ConversionService conversionService) {
180                        this.fieldType = fieldType;
181                        this.parser = parser;
182                        this.conversionService = conversionService;
183                }
184
185                @Override
186                public Set<ConvertiblePair> getConvertibleTypes() {
187                        return Collections.singleton(new ConvertiblePair(String.class, this.fieldType));
188                }
189
190                @Override
191                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
192                        String text = (String) source;
193                        if (!StringUtils.hasText(text)) {
194                                return null;
195                        }
196                        Object result;
197                        try {
198                                result = this.parser.parse(text, LocaleContextHolder.getLocale());
199                        }
200                        catch (IllegalArgumentException ex) {
201                                throw ex;
202                        }
203                        catch (Throwable ex) {
204                                throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
205                        }
206                        if (result == null) {
207                                throw new IllegalStateException("Parsers are not allowed to return null: " + this.parser);
208                        }
209                        TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass());
210                        if (!resultType.isAssignableTo(targetType)) {
211                                result = this.conversionService.convert(result, resultType, targetType);
212                        }
213                        return result;
214                }
215
216                @Override
217                public String toString() {
218                        return (String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser);
219                }
220        }
221
222
223        private class AnnotationPrinterConverter implements ConditionalGenericConverter {
224
225                private final Class<? extends Annotation> annotationType;
226
227                @SuppressWarnings("rawtypes")
228                private final AnnotationFormatterFactory annotationFormatterFactory;
229
230                private final Class<?> fieldType;
231
232                public AnnotationPrinterConverter(Class<? extends Annotation> annotationType,
233                                AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
234
235                        this.annotationType = annotationType;
236                        this.annotationFormatterFactory = annotationFormatterFactory;
237                        this.fieldType = fieldType;
238                }
239
240                @Override
241                public Set<ConvertiblePair> getConvertibleTypes() {
242                        return Collections.singleton(new ConvertiblePair(this.fieldType, String.class));
243                }
244
245                @Override
246                public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
247                        return sourceType.hasAnnotation(this.annotationType);
248                }
249
250                @Override
251                @SuppressWarnings("unchecked")
252                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
253                        Annotation ann = sourceType.getAnnotation(this.annotationType);
254                        if (ann == null) {
255                                throw new IllegalStateException(
256                                                "Expected [" + this.annotationType.getName() + "] to be present on " + sourceType);
257                        }
258                        AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, sourceType.getObjectType());
259                        GenericConverter converter = cachedPrinters.get(converterKey);
260                        if (converter == null) {
261                                Printer<?> printer = this.annotationFormatterFactory.getPrinter(
262                                                converterKey.getAnnotation(), converterKey.getFieldType());
263                                converter = new PrinterConverter(this.fieldType, printer, FormattingConversionService.this);
264                                cachedPrinters.put(converterKey, converter);
265                        }
266                        return converter.convert(source, sourceType, targetType);
267                }
268
269                @Override
270                public String toString() {
271                        return ("@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " +
272                                        String.class.getName() + ": " + this.annotationFormatterFactory);
273                }
274        }
275
276
277        private class AnnotationParserConverter implements ConditionalGenericConverter {
278
279                private final Class<? extends Annotation> annotationType;
280
281                @SuppressWarnings("rawtypes")
282                private final AnnotationFormatterFactory annotationFormatterFactory;
283
284                private final Class<?> fieldType;
285
286                public AnnotationParserConverter(Class<? extends Annotation> annotationType,
287                                AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {
288
289                        this.annotationType = annotationType;
290                        this.annotationFormatterFactory = annotationFormatterFactory;
291                        this.fieldType = fieldType;
292                }
293
294                @Override
295                public Set<ConvertiblePair> getConvertibleTypes() {
296                        return Collections.singleton(new ConvertiblePair(String.class, fieldType));
297                }
298
299                @Override
300                public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
301                        return targetType.hasAnnotation(this.annotationType);
302                }
303
304                @Override
305                @SuppressWarnings("unchecked")
306                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
307                        Annotation ann = targetType.getAnnotation(this.annotationType);
308                        if (ann == null) {
309                                throw new IllegalStateException(
310                                                "Expected [" + this.annotationType.getName() + "] to be present on " + targetType);
311                        }
312                        AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType());
313                        GenericConverter converter = cachedParsers.get(converterKey);
314                        if (converter == null) {
315                                Parser<?> parser = this.annotationFormatterFactory.getParser(
316                                                converterKey.getAnnotation(), converterKey.getFieldType());
317                                converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this);
318                                cachedParsers.put(converterKey, converter);
319                        }
320                        return converter.convert(source, sourceType, targetType);
321                }
322
323                @Override
324                public String toString() {
325                        return (String.class.getName() + " -> @" + this.annotationType.getName() + " " +
326                                        this.fieldType.getName() + ": " + this.annotationFormatterFactory);
327                }
328        }
329
330
331        private static class AnnotationConverterKey {
332
333                private final Annotation annotation;
334
335                private final Class<?> fieldType;
336
337                public AnnotationConverterKey(Annotation annotation, Class<?> fieldType) {
338                        this.annotation = annotation;
339                        this.fieldType = fieldType;
340                }
341
342                public Annotation getAnnotation() {
343                        return this.annotation;
344                }
345
346                public Class<?> getFieldType() {
347                        return this.fieldType;
348                }
349
350                @Override
351                public boolean equals(Object other) {
352                        if (this == other) {
353                                return true;
354                        }
355                        AnnotationConverterKey otherKey = (AnnotationConverterKey) other;
356                        return (this.fieldType == otherKey.fieldType && this.annotation.equals(otherKey.annotation));
357                }
358
359                @Override
360                public int hashCode() {
361                        return (this.fieldType.hashCode() * 29 + this.annotation.hashCode());
362                }
363        }
364
365}