@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
/ *
* Copyright 2012 - 2024 the original author or authors .
* Copyright 2012 - 2025 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 .
@ -17,12 +17,24 @@
@@ -17,12 +17,24 @@
package org.springframework.boot.convert ;
import java.lang.annotation.Annotation ;
import java.util.LinkedHashSet ;
import java.util.LinkedHashMap ;
import java.util.Map ;
import java.util.Optional ;
import java.util.Set ;
import java.util.function.BiFunction ;
import java.util.function.Consumer ;
import java.util.function.Supplier ;
import org.springframework.beans.factory.ListableBeanFactory ;
import org.springframework.beans.factory.config.BeanDefinition ;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory ;
import org.springframework.context.ConfigurableApplicationContext ;
import org.springframework.context.i18n.LocaleContextHolder ;
import org.springframework.core.ResolvableType ;
import org.springframework.core.convert.ConversionService ;
import org.springframework.core.convert.TypeDescriptor ;
import org.springframework.core.convert.converter.ConditionalConverter ;
import org.springframework.core.convert.converter.ConditionalGenericConverter ;
import org.springframework.core.convert.converter.Converter ;
import org.springframework.core.convert.converter.ConverterFactory ;
import org.springframework.core.convert.converter.ConverterRegistry ;
@ -37,6 +49,8 @@ import org.springframework.format.Parser;
@@ -37,6 +49,8 @@ import org.springframework.format.Parser;
import org.springframework.format.Printer ;
import org.springframework.format.support.DefaultFormattingConversionService ;
import org.springframework.format.support.FormattingConversionService ;
import org.springframework.util.Assert ;
import org.springframework.util.StringUtils ;
import org.springframework.util.StringValueResolver ;
/ * *
@ -49,10 +63,13 @@ import org.springframework.util.StringValueResolver;
@@ -49,10 +63,13 @@ import org.springframework.util.StringValueResolver;
* against registry instance .
*
* @author Phillip Webb
* @author Shixiong Guo
* @since 2 . 0 . 0
* /
public class ApplicationConversionService extends FormattingConversionService {
private static final ResolvableType STRING = ResolvableType . forClass ( String . class ) ;
private static volatile ApplicationConversionService sharedInstance ;
private final boolean unmodifiable ;
@ -265,35 +282,284 @@ public class ApplicationConversionService extends FormattingConversionService {
@@ -265,35 +282,284 @@ public class ApplicationConversionService extends FormattingConversionService {
}
/ * *
* Add { @link GenericConverter } , { @link Converter } , { @link Printer } , { @link Parser }
* and { @link Formatter } beans from the specified context .
* Add { @link Printer } , { @link Parser } , { @link Formatter } , { @link Converter } ,
* { @link ConverterFactory } , { @link GenericConverter } , and beans from the specified
* bean factory .
* @param registry the service to register beans with
* @param beanFactory the bean factory to get the beans from
* @since 2 . 2 . 0
* /
public static void addBeans ( FormatterRegistry registry , ListableBeanFactory beanFactory ) {
Set < Object > beans = new LinkedHashSet < > ( ) ;
beans . addAll ( beanFactory . getBeansOfType ( GenericConverter . class ) . values ( ) ) ;
beans . addAll ( beanFactory . getBeansOfType ( Converter . class ) . values ( ) ) ;
beans . addAll ( beanFactory . getBeansOfType ( Printer . class ) . values ( ) ) ;
beans . addAll ( beanFactory . getBeansOfType ( Parser . class ) . values ( ) ) ;
for ( Object bean : beans ) {
if ( bean instanceof GenericConverter genericConverter ) {
registry . addConverter ( genericConverter ) ;
}
else if ( bean instanceof Converter < ? , ? > converter ) {
registry . addConverter ( converter ) ;
}
else if ( bean instanceof Formatter < ? > formatter ) {
registry . addFormatter ( formatter ) ;
ConfigurableListableBeanFactory configurableBeanFactory = getConfigurableListableBeanFactory ( beanFactory ) ;
getBeans ( beanFactory ) . forEach ( ( beanName , bean ) - > {
BeanDefinition beanDefinition = ( configurableBeanFactory ! = null )
? configurableBeanFactory . getMergedBeanDefinition ( beanName ) : null ;
ResolvableType type = ( beanDefinition ! = null ) ? beanDefinition . getResolvableType ( ) : null ;
addBean ( registry , bean , type ) ;
} ) ;
}
private static ConfigurableListableBeanFactory getConfigurableListableBeanFactory ( ListableBeanFactory beanFactory ) {
if ( beanFactory instanceof ConfigurableApplicationContext applicationContext ) {
return applicationContext . getBeanFactory ( ) ;
}
if ( beanFactory instanceof ConfigurableListableBeanFactory configurableListableBeanFactory ) {
return configurableListableBeanFactory ;
}
return null ;
}
private static Map < String , Object > getBeans ( ListableBeanFactory beanFactory ) {
Map < String , Object > beans = new LinkedHashMap < > ( ) ;
beans . putAll ( getBeans ( beanFactory , Printer . class ) ) ;
beans . putAll ( getBeans ( beanFactory , Parser . class ) ) ;
beans . putAll ( getBeans ( beanFactory , Formatter . class ) ) ;
beans . putAll ( getBeans ( beanFactory , Converter . class ) ) ;
beans . putAll ( getBeans ( beanFactory , ConverterFactory . class ) ) ;
beans . putAll ( getBeans ( beanFactory , GenericConverter . class ) ) ;
return beans ;
}
private static < T > Map < String , T > getBeans ( ListableBeanFactory beanFactory , Class < T > type ) {
return beanFactory . getBeansOfType ( type ) ;
}
static void addBean ( FormatterRegistry registry , Object bean , ResolvableType beanType ) {
if ( bean instanceof GenericConverter converterBean ) {
addBean ( registry , converterBean , beanType , GenericConverter . class , registry : : addConverter , ( Runnable ) null ) ;
}
else if ( bean instanceof Converter < ? , ? > converterBean ) {
addBean ( registry , converterBean , beanType , Converter . class , registry : : addConverter ,
ConverterBeanAdapter : : new ) ;
}
else if ( bean instanceof ConverterFactory < ? , ? > converterBean ) {
addBean ( registry , converterBean , beanType , ConverterFactory . class , registry : : addConverterFactory ,
ConverterFactoryBeanAdapter : : new ) ;
}
else if ( bean instanceof Formatter < ? > formatterBean ) {
addBean ( registry , formatterBean , beanType , Formatter . class , registry : : addFormatter , ( ) - > {
registry . addConverter ( new PrinterBeanAdapter ( formatterBean , beanType ) ) ;
registry . addConverter ( new ParserBeanAdapter ( formatterBean , beanType ) ) ;
} ) ;
}
else if ( bean instanceof Printer < ? > printerBean ) {
addBean ( registry , printerBean , beanType , Printer . class , registry : : addPrinter , PrinterBeanAdapter : : new ) ;
}
else if ( bean instanceof Parser < ? > parserBean ) {
addBean ( registry , parserBean , beanType , Parser . class , registry : : addParser , ParserBeanAdapter : : new ) ;
}
}
private static < B , T > void addBean ( FormatterRegistry registry , B bean , ResolvableType beanType , Class < T > type ,
Consumer < B > standardRegistrar , BiFunction < B , ResolvableType , BeanAdapter < ? > > beanAdapterFactory ) {
addBean ( registry , bean , beanType , type , standardRegistrar ,
( ) - > registry . addConverter ( beanAdapterFactory . apply ( bean , beanType ) ) ) ;
}
private static < B , T > void addBean ( FormatterRegistry registry , B bean , ResolvableType beanType , Class < T > type ,
Consumer < B > standardRegistrar , Runnable beanAdapterRegistrar ) {
if ( beanType ! = null & & beanAdapterRegistrar ! = null
& & ResolvableType . forInstance ( bean ) . as ( type ) . hasUnresolvableGenerics ( ) ) {
beanAdapterRegistrar . run ( ) ;
return ;
}
standardRegistrar . accept ( bean ) ;
}
/ * *
* Base class for adapters that adapt a bean to a { @link GenericConverter } .
*
* @param < B > the base type of the bean
* /
abstract static class BeanAdapter < B > implements ConditionalGenericConverter {
private final B bean ;
private final ResolvableTypePair types ;
BeanAdapter ( B bean , ResolvableType beanType ) {
Assert . isInstanceOf ( beanType . toClass ( ) , bean ) ;
ResolvableType type = ResolvableType . forClass ( getClass ( ) ) . as ( BeanAdapter . class ) . getGeneric ( ) ;
ResolvableType [ ] generics = beanType . as ( type . toClass ( ) ) . getGenerics ( ) ;
this . bean = bean ;
this . types = getResolvableTypePair ( generics ) ;
}
protected ResolvableTypePair getResolvableTypePair ( ResolvableType [ ] generics ) {
return new ResolvableTypePair ( generics [ 0 ] , generics [ 1 ] ) ;
}
protected B bean ( ) {
return this . bean ;
}
@Override
public Set < ConvertiblePair > getConvertibleTypes ( ) {
return Set . of ( new ConvertiblePair ( this . types . source ( ) . toClass ( ) , this . types . target ( ) . toClass ( ) ) ) ;
}
@Override
public boolean matches ( TypeDescriptor sourceType , TypeDescriptor targetType ) {
return ( this . types . target ( ) . toClass ( ) = = targetType . getObjectType ( )
& & matchesTargetType ( targetType . getResolvableType ( ) ) ) ;
}
private boolean matchesTargetType ( ResolvableType targetType ) {
ResolvableType ours = this . types . target ( ) ;
return targetType . getType ( ) instanceof Class | | targetType . isAssignableFrom ( ours )
| | this . types . target ( ) . hasUnresolvableGenerics ( ) ;
}
protected final boolean conditionalConverterCandidateMatches ( Object conditionalConverterCandidate ,
TypeDescriptor sourceType , TypeDescriptor targetType ) {
return ( conditionalConverterCandidate instanceof ConditionalConverter conditionalConverter )
? conditionalConverter . matches ( sourceType , targetType ) : true ;
}
@SuppressWarnings ( { "unchecked" , "rawtypes" } )
protected final Object convert ( Object source , TypeDescriptor targetType , Converter < ? , ? > converter ) {
return ( source ! = null ) ? ( ( Converter ) converter ) . convert ( source ) : convertNull ( targetType ) ;
}
private Object convertNull ( TypeDescriptor targetType ) {
return ( targetType . getObjectType ( ) ! = Optional . class ) ? null : Optional . empty ( ) ;
}
@Override
public String toString ( ) {
return this . types + " : " + this . bean ;
}
}
/ * *
* Adapts a { @link Printer } bean to a { @link GenericConverter } .
* /
static class PrinterBeanAdapter extends BeanAdapter < Printer < ? > > {
PrinterBeanAdapter ( Printer < ? > bean , ResolvableType beanType ) {
super ( bean , beanType ) ;
}
@Override
protected ResolvableTypePair getResolvableTypePair ( ResolvableType [ ] generics ) {
return new ResolvableTypePair ( generics [ 0 ] , STRING ) ;
}
@Override
public Object convert ( Object source , TypeDescriptor sourceType , TypeDescriptor targetType ) {
return ( source ! = null ) ? print ( source ) : "" ;
}
@SuppressWarnings ( "unchecked" )
private String print ( Object object ) {
return ( ( Printer < Object > ) bean ( ) ) . print ( object , LocaleContextHolder . getLocale ( ) ) ;
}
}
/ * *
* Adapts a { @link Parser } bean to a { @link GenericConverter } .
* /
static class ParserBeanAdapter extends BeanAdapter < Parser < ? > > {
ParserBeanAdapter ( Parser < ? > bean , ResolvableType beanType ) {
super ( bean , beanType ) ;
}
@Override
protected ResolvableTypePair getResolvableTypePair ( ResolvableType [ ] generics ) {
return new ResolvableTypePair ( STRING , generics [ 0 ] ) ;
}
@Override
public Object convert ( Object source , TypeDescriptor sourceType , TypeDescriptor targetType ) {
String text = ( String ) source ;
return ( ! StringUtils . hasText ( text ) ) ? null : parse ( text ) ;
}
private Object parse ( String text ) {
try {
return bean ( ) . parse ( text , LocaleContextHolder . getLocale ( ) ) ;
}
else if ( bean instanceof Printer < ? > printer ) {
registry . addPrinter ( printer ) ;
catch ( IllegalArgumentException ex ) {
throw ex ;
}
else if ( bean instanceof Parser < ? > parser ) {
registry . addParser ( parser ) ;
catch ( Throwable ex ) {
throw new IllegalArgumentException ( "Parse attempt failed for value [" + text + "]" , ex ) ;
}
}
}
/ * *
* Adapts a { @link Converter } bean to a { @link GenericConverter } .
* /
static final class ConverterBeanAdapter extends BeanAdapter < Converter < ? , ? > > {
ConverterBeanAdapter ( Converter < ? , ? > bean , ResolvableType beanType ) {
super ( bean , beanType ) ;
}
@Override
public boolean matches ( TypeDescriptor sourceType , TypeDescriptor targetType ) {
return super . matches ( sourceType , targetType )
& & conditionalConverterCandidateMatches ( bean ( ) , sourceType , targetType ) ;
}
@Override
public Object convert ( Object source , TypeDescriptor sourceType , TypeDescriptor targetType ) {
return convert ( source , targetType , bean ( ) ) ;
}
}
/ * *
* Adapts a { @link ConverterFactory } bean to a { @link GenericConverter } .
* /
private static final class ConverterFactoryBeanAdapter extends BeanAdapter < ConverterFactory < ? , ? > > {
ConverterFactoryBeanAdapter ( ConverterFactory < ? , ? > bean , ResolvableType beanType ) {
super ( bean , beanType ) ;
}
@Override
public boolean matches ( TypeDescriptor sourceType , TypeDescriptor targetType ) {
return super . matches ( sourceType , targetType )
& & conditionalConverterCandidateMatches ( bean ( ) , sourceType , targetType )
& & conditionalConverterCandidateMatches ( getConverter ( targetType : : getType ) , sourceType , targetType ) ;
}
@Override
public Object convert ( Object source , TypeDescriptor sourceType , TypeDescriptor targetType ) {
return convert ( source , targetType , getConverter ( targetType : : getObjectType ) ) ;
}
@SuppressWarnings ( { "unchecked" , "rawtypes" } )
private Converter < Object , ? > getConverter ( Supplier < Class < ? > > typeSupplier ) {
return ( ( ConverterFactory ) bean ( ) ) . getConverter ( typeSupplier . get ( ) ) ;
}
}
/ * *
* Convertible type information as extracted from bean generics .
*
* @param source the source type
* @param target the target type
* /
record ResolvableTypePair ( ResolvableType source , ResolvableType target ) {
ResolvableTypePair {
Assert . notNull ( source . resolve ( ) , "'source' cannot be resolved" ) ;
Assert . notNull ( target . resolve ( ) , "'target' cannot be resolved" ) ;
}
@Override
public final String toString ( ) {
return source ( ) + " -> " + target ( ) ;
}
}
}