001/* 002 * Copyright 2002-2018 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.lang.Nullable; 040import org.springframework.util.Assert; 041import org.springframework.util.ClassUtils; 042import org.springframework.util.StringUtils; 043import org.springframework.util.StringValueResolver; 044 045/** 046 * A {@link org.springframework.core.convert.ConversionService} implementation 047 * designed to be configured as a {@link FormatterRegistry}. 048 * 049 * @author Keith Donald 050 * @author Juergen Hoeller 051 * @since 3.0 052 */ 053public class FormattingConversionService extends GenericConversionService 054 implements FormatterRegistry, EmbeddedValueResolverAware { 055 056 @Nullable 057 private StringValueResolver embeddedValueResolver; 058 059 private final Map<AnnotationConverterKey, GenericConverter> cachedPrinters = new ConcurrentHashMap<>(64); 060 061 private final Map<AnnotationConverterKey, GenericConverter> cachedParsers = new ConcurrentHashMap<>(64); 062 063 064 @Override 065 public void setEmbeddedValueResolver(StringValueResolver resolver) { 066 this.embeddedValueResolver = resolver; 067 } 068 069 070 @Override 071 public void addPrinter(Printer<?> printer) { 072 Class<?> fieldType = getFieldType(printer, Printer.class); 073 addConverter(new PrinterConverter(fieldType, printer, this)); 074 } 075 076 @Override 077 public void addParser(Parser<?> parser) { 078 Class<?> fieldType = getFieldType(parser, Parser.class); 079 addConverter(new ParserConverter(fieldType, parser, this)); 080 } 081 082 @Override 083 public void addFormatter(Formatter<?> formatter) { 084 addFormatterForFieldType(getFieldType(formatter), formatter); 085 } 086 087 @Override 088 public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { 089 addConverter(new PrinterConverter(fieldType, formatter, this)); 090 addConverter(new ParserConverter(fieldType, formatter, this)); 091 } 092 093 @Override 094 public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) { 095 addConverter(new PrinterConverter(fieldType, printer, this)); 096 addConverter(new ParserConverter(fieldType, parser, this)); 097 } 098 099 @Override 100 public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) { 101 Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory); 102 if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) { 103 ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver); 104 } 105 Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes(); 106 for (Class<?> fieldType : fieldTypes) { 107 addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType)); 108 addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType)); 109 } 110 } 111 112 113 static Class<?> getFieldType(Formatter<?> formatter) { 114 return getFieldType(formatter, Formatter.class); 115 } 116 117 private static <T> Class<?> getFieldType(T instance, Class<T> genericInterface) { 118 Class<?> fieldType = GenericTypeResolver.resolveTypeArgument(instance.getClass(), genericInterface); 119 if (fieldType == null && instance instanceof DecoratingProxy) { 120 fieldType = GenericTypeResolver.resolveTypeArgument( 121 ((DecoratingProxy) instance).getDecoratedClass(), genericInterface); 122 } 123 Assert.notNull(fieldType, () -> "Unable to extract the parameterized field type from " + 124 ClassUtils.getShortName(genericInterface) + " [" + instance.getClass().getName() + 125 "]; does the class parameterize the <T> generic type?"); 126 return fieldType; 127 } 128 129 @SuppressWarnings("unchecked") 130 static Class<? extends Annotation> getAnnotationType(AnnotationFormatterFactory<? extends Annotation> factory) { 131 Class<? extends Annotation> annotationType = (Class<? extends Annotation>) 132 GenericTypeResolver.resolveTypeArgument(factory.getClass(), AnnotationFormatterFactory.class); 133 if (annotationType == null) { 134 throw new IllegalArgumentException("Unable to extract parameterized Annotation type argument from " + 135 "AnnotationFormatterFactory [" + factory.getClass().getName() + 136 "]; does the factory parameterize the <A extends Annotation> generic type?"); 137 } 138 return annotationType; 139 } 140 141 142 private static class PrinterConverter implements GenericConverter { 143 144 private final Class<?> fieldType; 145 146 private final TypeDescriptor printerObjectType; 147 148 @SuppressWarnings("rawtypes") 149 private final Printer printer; 150 151 private final ConversionService conversionService; 152 153 public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) { 154 this.fieldType = fieldType; 155 this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer)); 156 this.printer = printer; 157 this.conversionService = conversionService; 158 } 159 160 @Override 161 public Set<ConvertiblePair> getConvertibleTypes() { 162 return Collections.singleton(new ConvertiblePair(this.fieldType, String.class)); 163 } 164 165 @Override 166 @SuppressWarnings("unchecked") 167 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 168 if (!sourceType.isAssignableTo(this.printerObjectType)) { 169 source = this.conversionService.convert(source, sourceType, this.printerObjectType); 170 } 171 if (source == null) { 172 return ""; 173 } 174 return this.printer.print(source, LocaleContextHolder.getLocale()); 175 } 176 177 @Nullable 178 private Class<?> resolvePrinterObjectType(Printer<?> printer) { 179 return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class); 180 } 181 182 @Override 183 public String toString() { 184 return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer); 185 } 186 } 187 188 189 private static class ParserConverter implements GenericConverter { 190 191 private final Class<?> fieldType; 192 193 private final Parser<?> parser; 194 195 private final ConversionService conversionService; 196 197 public ParserConverter(Class<?> fieldType, Parser<?> parser, ConversionService conversionService) { 198 this.fieldType = fieldType; 199 this.parser = parser; 200 this.conversionService = conversionService; 201 } 202 203 @Override 204 public Set<ConvertiblePair> getConvertibleTypes() { 205 return Collections.singleton(new ConvertiblePair(String.class, this.fieldType)); 206 } 207 208 @Override 209 @Nullable 210 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 211 String text = (String) source; 212 if (!StringUtils.hasText(text)) { 213 return null; 214 } 215 Object result; 216 try { 217 result = this.parser.parse(text, LocaleContextHolder.getLocale()); 218 } 219 catch (IllegalArgumentException ex) { 220 throw ex; 221 } 222 catch (Throwable ex) { 223 throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex); 224 } 225 TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass()); 226 if (!resultType.isAssignableTo(targetType)) { 227 result = this.conversionService.convert(result, resultType, targetType); 228 } 229 return result; 230 } 231 232 @Override 233 public String toString() { 234 return (String.class.getName() + " -> " + this.fieldType.getName() + ": " + this.parser); 235 } 236 } 237 238 239 private class AnnotationPrinterConverter implements ConditionalGenericConverter { 240 241 private final Class<? extends Annotation> annotationType; 242 243 @SuppressWarnings("rawtypes") 244 private final AnnotationFormatterFactory annotationFormatterFactory; 245 246 private final Class<?> fieldType; 247 248 public AnnotationPrinterConverter(Class<? extends Annotation> annotationType, 249 AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) { 250 251 this.annotationType = annotationType; 252 this.annotationFormatterFactory = annotationFormatterFactory; 253 this.fieldType = fieldType; 254 } 255 256 @Override 257 public Set<ConvertiblePair> getConvertibleTypes() { 258 return Collections.singleton(new ConvertiblePair(this.fieldType, String.class)); 259 } 260 261 @Override 262 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { 263 return sourceType.hasAnnotation(this.annotationType); 264 } 265 266 @Override 267 @SuppressWarnings("unchecked") 268 @Nullable 269 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 270 Annotation ann = sourceType.getAnnotation(this.annotationType); 271 if (ann == null) { 272 throw new IllegalStateException( 273 "Expected [" + this.annotationType.getName() + "] to be present on " + sourceType); 274 } 275 AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, sourceType.getObjectType()); 276 GenericConverter converter = cachedPrinters.get(converterKey); 277 if (converter == null) { 278 Printer<?> printer = this.annotationFormatterFactory.getPrinter( 279 converterKey.getAnnotation(), converterKey.getFieldType()); 280 converter = new PrinterConverter(this.fieldType, printer, FormattingConversionService.this); 281 cachedPrinters.put(converterKey, converter); 282 } 283 return converter.convert(source, sourceType, targetType); 284 } 285 286 @Override 287 public String toString() { 288 return ("@" + this.annotationType.getName() + " " + this.fieldType.getName() + " -> " + 289 String.class.getName() + ": " + this.annotationFormatterFactory); 290 } 291 } 292 293 294 private class AnnotationParserConverter implements ConditionalGenericConverter { 295 296 private final Class<? extends Annotation> annotationType; 297 298 @SuppressWarnings("rawtypes") 299 private final AnnotationFormatterFactory annotationFormatterFactory; 300 301 private final Class<?> fieldType; 302 303 public AnnotationParserConverter(Class<? extends Annotation> annotationType, 304 AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) { 305 306 this.annotationType = annotationType; 307 this.annotationFormatterFactory = annotationFormatterFactory; 308 this.fieldType = fieldType; 309 } 310 311 @Override 312 public Set<ConvertiblePair> getConvertibleTypes() { 313 return Collections.singleton(new ConvertiblePair(String.class, this.fieldType)); 314 } 315 316 @Override 317 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { 318 return targetType.hasAnnotation(this.annotationType); 319 } 320 321 @Override 322 @SuppressWarnings("unchecked") 323 @Nullable 324 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { 325 Annotation ann = targetType.getAnnotation(this.annotationType); 326 if (ann == null) { 327 throw new IllegalStateException( 328 "Expected [" + this.annotationType.getName() + "] to be present on " + targetType); 329 } 330 AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType()); 331 GenericConverter converter = cachedParsers.get(converterKey); 332 if (converter == null) { 333 Parser<?> parser = this.annotationFormatterFactory.getParser( 334 converterKey.getAnnotation(), converterKey.getFieldType()); 335 converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this); 336 cachedParsers.put(converterKey, converter); 337 } 338 return converter.convert(source, sourceType, targetType); 339 } 340 341 @Override 342 public String toString() { 343 return (String.class.getName() + " -> @" + this.annotationType.getName() + " " + 344 this.fieldType.getName() + ": " + this.annotationFormatterFactory); 345 } 346 } 347 348 349 private static class AnnotationConverterKey { 350 351 private final Annotation annotation; 352 353 private final Class<?> fieldType; 354 355 public AnnotationConverterKey(Annotation annotation, Class<?> fieldType) { 356 this.annotation = annotation; 357 this.fieldType = fieldType; 358 } 359 360 public Annotation getAnnotation() { 361 return this.annotation; 362 } 363 364 public Class<?> getFieldType() { 365 return this.fieldType; 366 } 367 368 @Override 369 public boolean equals(@Nullable Object other) { 370 if (this == other) { 371 return true; 372 } 373 if (!(other instanceof AnnotationConverterKey)) { 374 return false; 375 } 376 AnnotationConverterKey otherKey = (AnnotationConverterKey) other; 377 return (this.fieldType == otherKey.fieldType && this.annotation.equals(otherKey.annotation)); 378 } 379 380 @Override 381 public int hashCode() { 382 return (this.fieldType.hashCode() * 29 + this.annotation.hashCode()); 383 } 384 } 385 386}