Add ApplicationConversionService and fix binder

Create a new `ApplicationConversionService` similar in design to the
DefaultFormattingConversionService from Spring Framework. The new
conversion service provides a central place for custom conversion logic
supported by Spring Boot.

Also replace the `BindingConversionService` with an internal
`BindConverter` class that now invokes the `SimpleTypeConverter`
directly. Binding for `@ConfigurationProperties` has been updated so
that any custom property editors registered with the BeanFactory can
be used.

Fixes gh-12095
This commit is contained in:
Phillip Webb
2018-02-17 08:21:49 -08:00
parent 61f44179cb
commit 20109e27be
75 changed files with 2706 additions and 1354 deletions

View File

@@ -23,7 +23,7 @@ import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
@@ -67,7 +67,7 @@ public class CorsEndpointProperties {
* How long the response from a pre-flight request can be cached by clients. If a
* duration suffix is not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge = Duration.ofSeconds(1800);
public List<String> getAllowedOrigins() {

View File

@@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Meter.Type;
import org.springframework.boot.context.properties.bind.convert.DurationConverter;
import org.springframework.boot.convert.DurationStyle;
/**
* A service level agreement boundary for use when configuring micrometer. Can be
@@ -81,8 +81,7 @@ public final class ServiceLevelAgreementBoundary {
if (isNumber(value)) {
return new ServiceLevelAgreementBoundary(Long.parseLong(value));
}
return new ServiceLevelAgreementBoundary(
DurationConverter.toDuration(value, null));
return new ServiceLevelAgreementBoundary(DurationStyle.detectAndParse(value));
}
/**
@@ -97,8 +96,8 @@ public final class ServiceLevelAgreementBoundary {
/**
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given String
* value. The value may contain a simple number, or a {@link DurationConverter
* duration formatted value}
* value. The value may contain a simple number, or a {@link DurationStyle duration
* style string}.
* @param value the source value
* @return a {@link ServiceLevelAgreementBoundary} instance
*/

View File

@@ -19,9 +19,8 @@ package org.springframework.boot.actuate.endpoint.invoke.convert;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
/**
@@ -39,7 +38,7 @@ public class ConversionServiceParameterValueMapper implements ParameterValueMapp
* Create a new {@link ConversionServiceParameterValueMapper} instance.
*/
public ConversionServiceParameterValueMapper() {
this(createConversionService());
this(ApplicationConversionService.getSharedInstance());
}
/**
@@ -49,7 +48,7 @@ public class ConversionServiceParameterValueMapper implements ParameterValueMapp
*/
public ConversionServiceParameterValueMapper(ConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = new BinderConversionService(conversionService);
this.conversionService = conversionService;
}
@Override
@@ -63,10 +62,4 @@ public class ConversionServiceParameterValueMapper implements ParameterValueMapp
}
}
private static ConversionService createConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
IsoOffsetDateTimeConverter.registerConverter(conversionService);
return conversionService;
}
}

View File

@@ -24,7 +24,7 @@ import java.util.List;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory.CacheMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -80,7 +80,7 @@ public class RabbitProperties {
* Requested heartbeat timeout; zero for none. If a duration suffix is not specified,
* seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration requestedHeartbeat;
/**

View File

@@ -31,7 +31,7 @@ import com.datastax.driver.core.policies.ReconnectionPolicy;
import com.datastax.driver.core.policies.RetryPolicy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
/**
* Configuration properties for Cassandra.
@@ -283,7 +283,7 @@ public class CassandraProperties {
* Idle timeout before an idle connection is removed. If a duration suffix is not
* specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration idleTimeout = Duration.ofSeconds(120);
/**
@@ -296,7 +296,7 @@ public class CassandraProperties {
* sure it's still alive. If a duration suffix is not specified, seconds will be
* used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration heartbeatInterval = Duration.ofSeconds(30);
/**

View File

@@ -20,7 +20,7 @@ import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
/**
* Configuration properties for JDBC.
@@ -58,7 +58,7 @@ public class JdbcProperties {
* Query timeout. Default is to use the JDBC driver's default configuration. If a
* duration suffix is not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration queryTimeout;
public int getFetchSize() {

View File

@@ -33,7 +33,7 @@ import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.core.io.Resource;
import org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode;
import org.springframework.kafka.security.jaas.KafkaJaasLoginModuleInitializer;
@@ -840,7 +840,7 @@ public class KafkaProperties {
* Time between checks for non-responsive consumers. If a duration suffix is not
* specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration monitorInterval;
/**

View File

@@ -20,7 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.Delimiter;
import org.springframework.boot.convert.Delimiter;
import org.springframework.core.io.Resource;
/**

View File

@@ -22,7 +22,7 @@ import java.util.Map;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionException;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -107,7 +107,7 @@ final class OAuth2ClientPropertiesRegistrationAdapter {
private static CommonOAuth2Provider getCommonProvider(String providerId) {
try {
return new BinderConversionService(null).convert(providerId,
return ApplicationConversionService.getSharedInstance().convert(providerId,
CommonOAuth2Provider.class);
}
catch (ConversionException ex) {

View File

@@ -20,7 +20,7 @@ import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
/**
@@ -39,7 +39,7 @@ public class TransactionProperties implements
* Default transaction timeout. If a duration suffix is not specified, seconds will be
* used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration defaultTimeout;
/**

View File

@@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.http.CacheControl;
/**
@@ -277,7 +277,7 @@ public class ResourceProperties {
* suffix is not specified, seconds will be used. Can be overridden by the
* 'spring.resources.cache.cachecontrol' properties.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration period;
/**
@@ -307,7 +307,7 @@ public class ResourceProperties {
* Maximum time the response should be cached, in seconds if no duration
* suffix is not specified.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge;
/**
@@ -354,21 +354,21 @@ public class ResourceProperties {
* Maximum time the response can be served after it becomes stale, in seconds
* if no duration suffix is not specified.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleWhileRevalidate;
/**
* Maximum time the response may be used when errors are encountered, in
* seconds if no duration suffix is not specified.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleIfError;
/**
* Maximum time the response should be cached by shared caches, in seconds if
* no duration suffix is not specified.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration sMaxAge;
public Duration getMaxAge() {

View File

@@ -31,7 +31,7 @@ import java.util.TimeZone;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;
@@ -373,7 +373,7 @@ public class ServerProperties {
* Delay between the invocation of backgroundProcess methods. If a duration suffix
* is not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration backgroundProcessorDelay = Duration.ofSeconds(30);
/**

View File

@@ -18,7 +18,9 @@ package org.springframework.boot.context.properties;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
@@ -26,9 +28,12 @@ import org.springframework.boot.context.properties.bind.PropertySourcesPlacehold
import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler;
import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.PropertySources;
import org.springframework.util.Assert;
import org.springframework.validation.Validator;
@@ -115,13 +120,32 @@ class ConfigurationPropertiesBinder {
private Binder getBinder() {
if (this.binder == null) {
this.binder = new Binder(
ConfigurationPropertySources.from(this.propertySources),
new PropertySourcesPlaceholdersResolver(this.propertySources),
new ConversionServiceDeducer(this.applicationContext)
.getConversionService());
this.binder = new Binder(getConfigurationPropertySources(),
getPropertySourcesPlaceholdersResolver(), getConversionService(),
getPropertyEditorInitializer());
}
return this.binder;
}
private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
return ConfigurationPropertySources.from(this.propertySources);
}
private PropertySourcesPlaceholdersResolver getPropertySourcesPlaceholdersResolver() {
return new PropertySourcesPlaceholdersResolver(this.propertySources);
}
private ConversionService getConversionService() {
return new ConversionServiceDeducer(this.applicationContext)
.getConversionService();
}
private Consumer<PropertyEditorRegistry> getPropertyEditorInitializer() {
if (this.applicationContext instanceof ConfigurableApplicationContext) {
return ((ConfigurableApplicationContext) this.applicationContext)
.getBeanFactory()::copyRegisteredEditorsTo;
}
return null;
}
}

View File

@@ -21,12 +21,12 @@ import java.util.List;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
/**
* Utility to deduce the {@link ConversionService} to use for configuration properties
@@ -83,7 +83,10 @@ class ConversionServiceDeducer {
}
public ConversionService create() {
DefaultConversionService conversionService = new DefaultConversionService();
if (this.converters.isEmpty() && this.genericConverters.isEmpty()) {
return ApplicationConversionService.getSharedInstance();
}
ApplicationConversionService conversionService = new ApplicationConversionService();
for (Converter<?, ?> converter : this.converters) {
conversionService.addConverter(converter);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.boot.context.properties.bind;
import java.util.function.Supplier;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
@@ -30,9 +31,9 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS
*/
abstract class AggregateBinder<T> {
private final BindContext context;
private final Context context;
AggregateBinder(BindContext context) {
AggregateBinder(Context context) {
this.context = context;
}
@@ -84,7 +85,7 @@ abstract class AggregateBinder<T> {
* Return the context being used by this binder.
* @return the context
*/
protected final BindContext getContext() {
protected final Context getContext() {
return this.context;
}

View File

@@ -20,6 +20,7 @@ import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.ResolvableType;
@@ -31,7 +32,7 @@ import org.springframework.core.ResolvableType;
*/
class ArrayBinder extends IndexedElementsBinder<Object> {
ArrayBinder(BindContext context) {
ArrayBinder(Context context) {
super(context);
}

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
/**
@@ -36,7 +37,7 @@ interface BeanBinder {
* @param <T> The source type
* @return a bound instance or {@code null}
*/
<T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindContext context,
<T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
BeanPropertyBinder propertyBinder);
}

View File

@@ -16,12 +16,8 @@
package org.springframework.boot.context.properties.bind;
import java.util.stream.Stream;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.convert.ConversionService;
/**
* Context information for use by {@link BindHandler BindHandlers}.
@@ -46,13 +42,6 @@ public interface BindContext {
*/
Iterable<ConfigurationPropertySource> getSources();
/**
* Return a {@link Stream} of the {@link ConfigurationPropertySource sources} being
* used by the {@link Binder}.
* @return the sources
*/
Stream<ConfigurationPropertySource> streamSources();
/**
* Return the {@link ConfigurationProperty} actually being bound or {@code null} if
* the property has not yet been determined.
@@ -60,16 +49,4 @@ public interface BindContext {
*/
ConfigurationProperty getConfigurationProperty();
/**
* Return the {@link PlaceholdersResolver} being used by the binder.
* @return the {@link PlaceholdersResolver} (never {@code null})
*/
PlaceholdersResolver getPlaceholdersResolver();
/**
* Return the {@link ConversionService} used by the binder.
* @return the conversion service
*/
BinderConversionService getConversionService();
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2012-2018 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.boot.context.properties.bind;
import java.beans.PropertyEditor;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
/**
* Utility to handle any conversion needed during binding. This class is not thread-safe
* and so a new instance is created for each top-level bind.
*
* @author Phillip Webb
*/
class BindConverter {
private final ConversionService conversionService;
private final SimpleTypeConverter simpleTypeConverter;
BindConverter(ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
this.simpleTypeConverter = new SimpleTypeConverter();
if (propertyEditorInitializer != null) {
propertyEditorInitializer.accept(this.simpleTypeConverter);
}
}
public boolean canConvert(Object value, ResolvableType type,
Annotation... annotations) {
return getPropertyEditor(type.resolve()) != null
|| this.conversionService.canConvert(TypeDescriptor.forObject(value),
new ResolvableTypeDescriptor(type, annotations));
}
public <T> T convert(Object result, Bindable<T> target) {
return convert(result, target.getType(), target.getAnnotations());
}
@SuppressWarnings("unchecked")
public <T> T convert(Object value, ResolvableType type, Annotation... annotations) {
PropertyEditor propertyEditor = getPropertyEditor(type.resolve());
if (propertyEditor != null) {
if (value == null) {
return null;
}
return (T) this.simpleTypeConverter.convertIfNecessary(value, type.resolve());
}
return (T) this.conversionService.convert(value, TypeDescriptor.forObject(value),
new ResolvableTypeDescriptor(type, annotations));
}
private PropertyEditor getPropertyEditor(Class<?> type) {
if (type == null || type == Object.class
|| Collection.class.isAssignableFrom(type)
|| Map.class.isAssignableFrom(type)) {
return null;
}
PropertyEditor editor = this.simpleTypeConverter.getDefaultEditor(type);
if (editor == null) {
editor = this.simpleTypeConverter.findCustomEditor(type, null);
}
if (editor == null && String.class != type) {
editor = BeanUtils.findEditorByConvention(type);
}
return editor;
}
/**
* A {@link TypeDescriptor} backed by a {@link ResolvableType}.
*/
final class ResolvableTypeDescriptor extends TypeDescriptor {
ResolvableTypeDescriptor(ResolvableType resolvableType,
Annotation[] annotations) {
super(resolvableType, null, annotations);
}
}
}

View File

@@ -27,16 +27,18 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.ConfigurationPropertyState;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.env.Environment;
@@ -69,7 +71,9 @@ public class Binder {
private final PlaceholdersResolver placeholdersResolver;
private final BinderConversionService conversionService;
private final ConversionService conversionService;
private final Consumer<PropertyEditorRegistry> propertyEditorInitializer;
/**
* Create a new {@link Binder} instance for the specified sources. A
@@ -77,7 +81,7 @@ public class Binder {
* @param sources the sources used for binding
*/
public Binder(ConfigurationPropertySource... sources) {
this(Arrays.asList(sources), null, null);
this(Arrays.asList(sources), null, null, null);
}
/**
@@ -86,7 +90,7 @@ public class Binder {
* @param sources the sources used for binding
*/
public Binder(Iterable<ConfigurationPropertySource> sources) {
this(sources, null, null);
this(sources, null, null, null);
}
/**
@@ -96,25 +100,29 @@ public class Binder {
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
PlaceholdersResolver placeholdersResolver) {
this(sources, placeholdersResolver, null);
this(sources, placeholdersResolver, null, null);
}
/**
* Create a new {@link Binder} instance for the specified sources.
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property place-holders
* @param conversionService the conversion service to convert values
* @param conversionService the conversion service to convert values (or {@code null}
* to use {@link ApplicationConversionService})
* @param propertyEditorInitializer initializer used to configure the property editors
* that can convert values
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
PlaceholdersResolver placeholdersResolver,
ConversionService conversionService) {
ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
Assert.notNull(sources, "Sources must not be null");
this.sources = sources;
this.placeholdersResolver = (placeholdersResolver != null ? placeholdersResolver
: PlaceholdersResolver.NONE);
this.conversionService = (conversionService instanceof BinderConversionService
? (BinderConversionService) conversionService
: new BinderConversionService(conversionService));
this.conversionService = (conversionService != null ? conversionService
: ApplicationConversionService.getSharedInstance());
this.propertyEditorInitializer = propertyEditorInitializer;
}
/**
@@ -208,17 +216,17 @@ public class Binder {
BindHandler handler, Context context, Object result) throws Exception {
if (result != null) {
result = handler.onSuccess(name, target, context, result);
result = convert(result, target);
result = context.getConverter().convert(result, target);
}
handler.onFinish(name, target, context, result);
return convert(result, target);
return context.getConverter().convert(result, target);
}
private <T> T handleBindError(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler, Context context, Exception error) {
try {
Object result = handler.onFailure(name, target, context, error);
return convert(result, target);
return context.getConverter().convert(result, target);
}
catch (Exception ex) {
if (ex instanceof BindException) {
@@ -228,11 +236,6 @@ public class Binder {
}
}
private <T> T convert(Object value, Bindable<T> target) {
return ResolvableTypeDescriptor.forBindable(target)
.convert(this.conversionService, value);
}
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler, Context context, boolean allowRecursiveBinding)
throws Exception {
@@ -300,7 +303,7 @@ public class Binder {
context.setConfigurationProperty(property);
Object result = property.getValue();
result = this.placeholdersResolver.resolvePlaceholders(result);
result = convert(result, target);
result = context.getConverter().convert(result, target);
return result;
}
@@ -356,35 +359,37 @@ public class Binder {
}
/**
* {@link BindContext} implementation.
* Context used when binding and the {@link BindContext} implementation.
*/
final class Context implements BindContext {
private int depth;
private final BindConverter converter;
private int sourcePushCount;
private int depth;
private final List<ConfigurationPropertySource> source = Arrays
.asList((ConfigurationPropertySource) null);
private int sourcePushCount;
private final Deque<Class<?>> beans = new ArrayDeque<>();
private ConfigurationProperty configurationProperty;
void increaseDepth() {
Context() {
this.converter = new BindConverter(Binder.this.conversionService,
Binder.this.propertyEditorInitializer);
}
private void increaseDepth() {
this.depth++;
}
void decreaseDepth() {
private void decreaseDepth() {
this.depth--;
}
@Override
public int getDepth() {
return this.depth;
}
public <T> T withSource(ConfigurationPropertySource source,
private <T> T withSource(ConfigurationPropertySource source,
Supplier<T> supplier) {
if (source == null) {
return supplier.get();
@@ -399,7 +404,7 @@ public class Binder {
}
}
public <T> T withBean(Class<?> bean, Supplier<T> supplier) {
private <T> T withBean(Class<?> bean, Supplier<T> supplier) {
this.beans.push(bean);
try {
return withIncreasedDepth(supplier);
@@ -409,7 +414,11 @@ public class Binder {
}
}
public <T> T withIncreasedDepth(Supplier<T> supplier) {
private boolean hasBoundBean(Class<?> bean) {
return this.beans.contains(bean);
}
private <T> T withIncreasedDepth(Supplier<T> supplier) {
increaseDepth();
try {
return supplier.get();
@@ -419,6 +428,35 @@ public class Binder {
}
}
private void setConfigurationProperty(
ConfigurationProperty configurationProperty) {
this.configurationProperty = configurationProperty;
}
private void clearConfigurationProperty() {
this.configurationProperty = null;
}
public Stream<ConfigurationPropertySource> streamSources() {
if (this.sourcePushCount > 0) {
return this.source.stream();
}
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
}
public PlaceholdersResolver getPlaceholdersResolver() {
return Binder.this.placeholdersResolver;
}
public BindConverter getConverter() {
return this.converter;
}
@Override
public int getDepth() {
return this.depth;
}
@Override
public Iterable<ConfigurationPropertySource> getSources() {
if (this.sourcePushCount > 0) {
@@ -427,41 +465,11 @@ public class Binder {
return Binder.this.sources;
}
@Override
public Stream<ConfigurationPropertySource> streamSources() {
if (this.sourcePushCount > 0) {
return this.source.stream();
}
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
}
public boolean hasBoundBean(Class<?> bean) {
return this.beans.contains(bean);
}
@Override
public ConfigurationProperty getConfigurationProperty() {
return this.configurationProperty;
}
void setConfigurationProperty(ConfigurationProperty configurationProperty) {
this.configurationProperty = configurationProperty;
}
void clearConfigurationProperty() {
this.configurationProperty = null;
}
@Override
public PlaceholdersResolver getPlaceholdersResolver() {
return Binder.this.placeholdersResolver;
}
@Override
public BinderConversionService getConversionService() {
return Binder.this.conversionService;
}
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.context.properties.bind;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.CollectionFactory;
import org.springframework.core.ResolvableType;
@@ -31,7 +32,7 @@ import org.springframework.core.ResolvableType;
*/
class CollectionBinder extends IndexedElementsBinder<Collection<Object>> {
CollectionBinder(BindContext context) {
CollectionBinder(Context context) {
super(context);
}

View File

@@ -23,7 +23,7 @@ import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
@@ -45,7 +45,7 @@ abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
private static final String INDEX_ZERO = "[0]";
IndexedElementsBinder(BindContext context) {
IndexedElementsBinder(Context context) {
super(context);
}
@@ -140,9 +140,7 @@ abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
private <C> C convert(Object value, ResolvableType type, Annotation... annotations) {
value = getContext().getPlaceholdersResolver().resolvePlaceholders(value);
BinderConversionService conversionService = getContext().getConversionService();
return ResolvableTypeDescriptor.forType(type, annotations)
.convert(conversionService, value);
return getContext().getConverter().convert(value, type, annotations);
}
/**

View File

@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyState;
import org.springframework.core.MethodParameter;
@@ -40,8 +41,8 @@ import org.springframework.core.ResolvableType;
class JavaBeanBinder implements BeanBinder {
@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
BindContext context, BeanPropertyBinder propertyBinder) {
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
BeanPropertyBinder propertyBinder) {
boolean hasKnownBindableProperties = context.streamSources().anyMatch((
s) -> s.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT);
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);

View File

@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
@@ -41,7 +42,7 @@ class MapBinder extends AggregateBinder<Map<Object, Object>> {
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
.mapOf(String.class, String.class);
MapBinder(BindContext context) {
MapBinder(Context context) {
super(context);
}
@@ -62,10 +63,8 @@ class MapBinder extends AggregateBinder<Map<Object, Object>> {
if (!ConfigurationPropertyName.EMPTY.equals(name)) {
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property != null && !hasDescendants) {
Object value = getContext().getPlaceholdersResolver()
.resolvePlaceholders(property.getValue());
return ResolvableTypeDescriptor.forType(target.getType())
.convert(getContext().getConversionService(), value);
return getContext().getConverter().convert(property.getValue(),
target);
}
source = source.filter(name::isAncestorOf);
}
@@ -116,7 +115,8 @@ class MapBinder extends AggregateBinder<Map<Object, Object>> {
for (ConfigurationPropertyName name : (IterableConfigurationPropertySource) source) {
Bindable<?> valueBindable = getValueBindable(name);
ConfigurationPropertyName entryName = getEntryName(source, name);
Object key = convert(getKeyName(entryName), this.keyType);
Object key = getContext().getConverter()
.convert(getKeyName(entryName), this.keyType);
map.computeIfAbsent(key,
(k) -> this.elementBinder.bind(entryName, valueBindable));
}
@@ -172,17 +172,7 @@ class MapBinder extends AggregateBinder<Map<Object, Object>> {
}
Object value = property.getValue();
value = getContext().getPlaceholdersResolver().resolvePlaceholders(value);
return canConvert(value, this.valueType);
}
private boolean canConvert(Object source, ResolvableType targetType) {
return ResolvableTypeDescriptor.forType(targetType)
.canConvert(getContext().getConversionService(), source);
}
private Object convert(Object source, ResolvableType targetType) {
return ResolvableTypeDescriptor.forType(targetType)
.convert(getContext().getConversionService(), source);
return getContext().getConverter().canConvert(value, this.valueType);
}
private String getKeyName(ConfigurationPropertyName name) {

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
/**
* A {@link TypeDescriptor} backed by a {@link ResolvableType}.
*
* @author Phillip Webb
*/
@SuppressWarnings("serial")
final class ResolvableTypeDescriptor extends TypeDescriptor {
private ResolvableTypeDescriptor(ResolvableType resolvableType,
Annotation[] annotations) {
super(resolvableType, null, annotations);
}
/**
* Determine if the specified source object can be converted to this type.
* @param conversionService the backing conversion service
* @param source the source to check
* @return {@code true} if conversion can be performed
*/
public boolean canConvert(ConversionService conversionService, Object source) {
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
return conversionService.canConvert(sourceType, this);
}
/**
* Convert the given source object into this type.
* @param conversionService the source conversion service
* @param value the value to convert
* @param <T> the target type
* @return the converted value
* @throws ConversionException if a conversion exception occurred
*/
@SuppressWarnings("unchecked")
public <T> T convert(ConversionService conversionService, Object value) {
if (value == null) {
return null;
}
TypeDescriptor sourceType = TypeDescriptor.forObject(value);
return (T) conversionService.convert(value, sourceType, this);
}
/**
* Create a {@link TypeDescriptor} for the specified {@link Bindable}.
* @param bindable the bindable
* @return the type descriptor
*/
public static ResolvableTypeDescriptor forBindable(Bindable<?> bindable) {
return forType(bindable.getType(), bindable.getAnnotations());
}
/**
* Return a {@link TypeDescriptor} for the specified {@link ResolvableType}.
* @param type the resolvable type
* @param annotations the annotations to include
* @return the type descriptor
*/
public static ResolvableTypeDescriptor forType(ResolvableType type,
Annotation... annotations) {
return new ResolvableTypeDescriptor(type, annotations);
}
}

View File

@@ -1,128 +0,0 @@
/*
* Copyright 2012-2018 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.boot.context.properties.bind.convert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* {@link ConversionService} used by the {@link Binder}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 2.0.0
*/
public class BinderConversionService implements ConversionService {
private static final ConversionService defaultConversionService = new DefaultFormattingConversionService();
private final List<ConversionService> conversionServices;
/**
* Create a new {@link BinderConversionService} instance.
* @param conversionService and option root conversion service
*/
public BinderConversionService(ConversionService conversionService) {
List<ConversionService> conversionServices = new ArrayList<>();
conversionServices.add(createOverrideConversionService());
conversionServices.add(
conversionService != null ? conversionService : defaultConversionService);
conversionServices.add(createAdditionalConversionService());
this.conversionServices = Collections.unmodifiableList(conversionServices);
}
private ConversionService createOverrideConversionService() {
GenericConversionService service = new GenericConversionService();
service.addConverter(new DelimitedStringToCollectionConverter(this));
return service;
}
private ConversionService createAdditionalConversionService() {
DefaultFormattingConversionService service = new DefaultFormattingConversionService();
DefaultConversionService.addCollectionConverters(service);
service.addConverterFactory(new StringToEnumConverterFactory());
service.addConverter(new StringToCharArrayConverter());
service.addConverter(new StringToInetAddressConverter());
service.addConverter(new InetAddressToStringConverter());
service.addConverter(new PropertyEditorConverter());
service.addConverter(new DurationConverter());
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
DateFormatter formatter = new DateFormatter();
formatter.setIso(DateTimeFormat.ISO.DATE_TIME);
registrar.setFormatter(formatter);
registrar.registerFormatters(service);
return service;
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
for (ConversionService service : this.conversionServices) {
if (service.canConvert(sourceType, targetType)) {
return true;
}
}
return false;
}
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
for (ConversionService service : this.conversionServices) {
if (service.canConvert(sourceType, targetType)) {
return true;
}
}
return false;
}
@Override
public <T> T convert(Object source, Class<T> targetType) {
return callConversionServices((c) -> c.convert(source, targetType));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return callConversionServices((c) -> c.convert(source, sourceType, targetType));
}
private <T> T callConversionServices(Function<ConversionService, T> call) {
ConversionException exception = null;
for (ConversionService service : this.conversionServices) {
try {
return call.apply(service);
}
catch (ConversionException ex) {
exception = ex;
}
}
throw exception;
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright 2012-2018 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.boot.context.properties.bind.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link Converter} for {@link String} to {@link Duration}. Support
* {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form.
*
* @author Phillip Webb
* @since 2.0.0
*/
public class DurationConverter implements GenericConverter {
private static final Set<ConvertiblePair> TYPES;
static {
Set<ConvertiblePair> types = new LinkedHashSet<>();
types.add(new ConvertiblePair(String.class, Duration.class));
types.add(new ConvertiblePair(Integer.class, Duration.class));
TYPES = Collections.unmodifiableSet(types);
}
private static final Pattern ISO8601 = Pattern.compile("^[\\+\\-]?P.*$");
private static final Pattern SIMPLE = Pattern
.compile("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$");
private static final Map<String, ChronoUnit> UNITS;
static {
Map<String, ChronoUnit> units = new LinkedHashMap<>();
units.put("ns", ChronoUnit.NANOS);
units.put("ms", ChronoUnit.MILLIS);
units.put("s", ChronoUnit.SECONDS);
units.put("m", ChronoUnit.MINUTES);
units.put("h", ChronoUnit.HOURS);
units.put("d", ChronoUnit.DAYS);
UNITS = Collections.unmodifiableMap(units);
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return TYPES;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
DefaultDurationUnit defaultUnit = targetType
.getAnnotation(DefaultDurationUnit.class);
return toDuration(source.toString(),
(defaultUnit == null ? null : defaultUnit.value()));
}
/**
* Convert the specified source to a {@link Duration}.
* @param source the source to convert
* @param defaultUnit the default unit to use ({@code null} is treated as
* milliseconds)
* @return the duration
*/
public static Duration toDuration(String source, ChronoUnit defaultUnit) {
try {
if (!StringUtils.hasLength(source)) {
return null;
}
if (ISO8601.matcher(source).matches()) {
return Duration.parse(source);
}
Matcher matcher = SIMPLE.matcher(source);
Assert.state(matcher.matches(),
() -> "'" + source + "' is not a valid duration");
long amount = Long.parseLong(matcher.group(1));
ChronoUnit unit = getUnit(matcher.group(2), defaultUnit);
return Duration.of(amount, unit);
}
catch (Exception ex) {
throw new IllegalStateException("'" + source + "' is not a valid duration",
ex);
}
}
private static ChronoUnit getUnit(String value, ChronoUnit defaultUnit) {
if (StringUtils.isEmpty(value)) {
return (defaultUnit != null ? defaultUnit : ChronoUnit.MILLIS);
}
ChronoUnit unit = UNITS.get(value.toLowerCase());
Assert.state(unit != null, () -> "Unknown unit '" + value + "'");
return unit;
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import java.beans.PropertyEditor;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrySupport;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* {@link GenericConverter} that delegates to Java bean {@link PropertyEditor
* PropertyEditors}.
*
* @author Phillip Webb
*/
class PropertyEditorConverter implements GenericConverter, ConditionalConverter {
private static final Set<Class<?>> SKIPPED;
static {
Set<Class<?>> skipped = new LinkedHashSet<>();
skipped.add(Collection.class);
skipped.add(Map.class);
SKIPPED = Collections.unmodifiableSet(skipped);
}
/**
* Registry that can be used to check if conversion is supported. Since
* {@link PropertyEditor PropertyEditors} are not thread safe this can't be used for
* actual conversion.
*/
private final PropertyEditorRegistrySupport registry = new SimpleTypeConverter();
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return null;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> type = targetType.getType();
if (isSkipped(type)) {
return false;
}
PropertyEditor editor = this.registry.getDefaultEditor(type);
editor = (editor != null ? editor : BeanUtils.findEditorByConvention(type));
return editor != null;
}
private boolean isSkipped(Class<?> type) {
return SKIPPED.stream().anyMatch((c) -> c.isAssignableFrom(type));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return new SimpleTypeConverter().convertIfNecessary(source, targetType.getType());
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright 2012-2017 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.
*/
/**
* Conversion support for configuration properties binding.
*/
package org.springframework.boot.context.properties.bind.convert;

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2012-2018 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.boot.convert;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.StringValueResolver;
/**
* A specialization of {@link FormattingConversionService} configured by default with
* converters and formatters appropriate for most Spring Boot applications.
* <p>
* Designed for direct instantiation but also exposes the static
* {@link #addApplicationConverters} and
* {@link #addApplicationFormatters(FormatterRegistry)} utility methods for ad-hoc use
* against registry instance.
*
* @author Phillip Webb
* @since 2.0.0
*/
public class ApplicationConversionService extends FormattingConversionService {
private static volatile ApplicationConversionService sharedInstance;
public ApplicationConversionService() {
this(null);
}
public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
if (embeddedValueResolver != null) {
setEmbeddedValueResolver(embeddedValueResolver);
}
DefaultConversionService.addDefaultConverters(this);
DefaultFormattingConversionService.addDefaultFormatters(this);
addApplicationConverters(this);
addApplicationFormatters(this);
}
/**
* Return a shared default {@code ApplicationConversionService} instance, lazily
* building it once needed.
* @return the shared {@code ConversionService} instance (never {@code null})
*/
public static ConversionService getSharedInstance() {
ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
if (sharedInstance == null) {
synchronized (ApplicationConversionService.class) {
sharedInstance = ApplicationConversionService.sharedInstance;
if (sharedInstance == null) {
sharedInstance = new ApplicationConversionService();
ApplicationConversionService.sharedInstance = sharedInstance;
}
}
}
return sharedInstance;
}
public void addApplicationConverters(ConverterRegistry registry) {
ConversionService service = (ConversionService) registry;
registry.addConverter(new ArrayToDelimitedStringConverter(service));
registry.addConverter(new CollectionToDelimitedStringConverter(service));
registry.addConverter(new DelimitedStringToArrayConverter(service));
registry.addConverter(new DelimitedStringToCollectionConverter(service));
registry.addConverter(new StringToDurationConverter());
registry.addConverter(new DurationToStringConverter());
registry.addConverter(new NumberToDurationConverter());
registry.addConverter(new DurationToNumberConverter());
registry.addConverterFactory(new StringToEnumIgnoringCaseConverterFactory());
}
public void addApplicationFormatters(FormatterRegistry registry) {
registry.addFormatter(new CharArrayFormatter());
registry.addFormatter(new InetAddressFormatter());
registry.addFormatter(new IsoOffsetFormatter());
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Converts an array to a delimited String.
*
* @author Phillip Webb
*/
final class ArrayToDelimitedStringConverter implements ConditionalGenericConverter {
private final CollectionToDelimitedStringConverter delegate;
ArrayToDelimitedStringConverter(ConversionService conversionService) {
this.delegate = new CollectionToDelimitedStringConverter(conversionService);
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object[].class, String.class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.delegate.matches(sourceType, targetType);
}
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
List<Object> list = Arrays.asList(ObjectUtils.toObjectArray(source));
return this.delegate.convert(list, sourceType, targetType);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@@ -14,24 +14,28 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.beans.PropertyEditor;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.Locale;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter;
/**
* {@link PropertyEditor} for {@link InetAddress} objects.
* {@link Formatter} for {@code char[]}.
*
* @author Dave Syer
* @author Phillip Webb
*/
class InetAddressToStringConverter implements Converter<InetAddress, String> {
final class CharArrayFormatter implements Formatter<char[]> {
@Override
public String convert(InetAddress source) {
return source.getHostAddress();
public String print(char[] object, Locale locale) {
return new String(object);
}
@Override
public char[] parse(String text, Locale locale) throws ParseException {
return text.toCharArray();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
/**
* Converts a Collection to a delimited String.
*
* @author Phillip Webb
*/
final class CollectionToDelimitedStringConverter implements ConditionalGenericConverter {
private final ConversionService conversionService;
CollectionToDelimitedStringConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Collection.class, String.class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
if (targetType == null || sourceElementType == null) {
return true;
}
if (this.conversionService.canConvert(sourceElementType, targetType)
|| sourceElementType.getType().isAssignableFrom(targetType.getType())) {
return true;
}
return false;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
Collection<?> sourceCollection = (Collection<?>) source;
return convert(sourceCollection, sourceType, targetType);
}
private Object convert(Collection<?> source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source.isEmpty()) {
return "";
}
Delimiter delimiter = sourceType.getAnnotation(Delimiter.class);
return source.stream()
.map((element) -> convertElement(element, sourceType, targetType))
.collect(Collectors.joining(delimiter == null ? "," : delimiter.value()));
}
private String convertElement(Object element, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return String.valueOf(this.conversionService.convert(element,
sourceType.elementTypeDescriptor(element), targetType));
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Converts a {@link Delimiter delimited} String to an Array.
*
* @author Phillip Webb
*/
final class DelimitedStringToArrayConverter implements ConditionalGenericConverter {
private final ConversionService conversionService;
DelimitedStringToArrayConverter(ConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Object[].class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return targetType.getElementTypeDescriptor() == null || this.conversionService
.canConvert(sourceType, targetType.getElementTypeDescriptor());
}
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
return convert((String) source, sourceType, targetType);
}
private Object convert(String source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
Delimiter delimiter = targetType.getAnnotation(Delimiter.class);
String[] elements = getElements(source,
(delimiter == null ? "," : delimiter.value()));
TypeDescriptor elementDescriptor = targetType.getElementTypeDescriptor();
Object target = Array.newInstance(elementDescriptor.getType(), elements.length);
for (int i = 0; i < elements.length; i++) {
String sourceElement = elements[i];
Object targetElement = this.conversionService.convert(sourceElement.trim(),
sourceType, elementDescriptor);
Array.set(target, i, targetElement);
}
return target;
}
private String[] getElements(String source, String delimiter) {
return StringUtils.delimitedListToStringArray(source,
Delimiter.NONE.equals(delimiter) ? null : delimiter);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.util.Arrays;
import java.util.Collection;
@@ -35,7 +35,7 @@ import org.springframework.util.StringUtils;
*
* @author Phillip Webb
*/
class DelimitedStringToCollectionConverter implements ConditionalGenericConverter {
final class DelimitedStringToCollectionConverter implements ConditionalGenericConverter {
private final ConversionService conversionService;
@@ -51,10 +51,8 @@ class DelimitedStringToCollectionConverter implements ConditionalGenericConverte
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return targetType.hasAnnotation(Delimiter.class)
&& (targetType.getElementTypeDescriptor() == null
|| this.conversionService.canConvert(sourceType,
targetType.getElementTypeDescriptor()));
return targetType.getElementTypeDescriptor() == null || this.conversionService
.canConvert(sourceType, targetType.getElementTypeDescriptor());
}
@Override
@@ -70,8 +68,8 @@ class DelimitedStringToCollectionConverter implements ConditionalGenericConverte
private Object convert(String source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
Delimiter delimiter = targetType.getAnnotation(Delimiter.class);
Assert.state(delimiter != null, "Missing @DelimitedStringFormat annotation");
String[] elements = getElements(source, delimiter.value());
String[] elements = getElements(source,
(delimiter == null ? "," : delimiter.value()));
TypeDescriptor elementDescriptor = targetType.getElementTypeDescriptor();
Collection<Object> target = createCollection(targetType, elementDescriptor,
elements.length);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;

View File

@@ -14,20 +14,31 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import org.springframework.core.convert.converter.Converter;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.Duration;
/**
* Converts a String to a Char Array.
* Annotation that can be used to indivate the format to use when converting a
* {@link Duration}.
*
* @author Phillip Webb
* @since 2.0.0
*/
class StringToCharArrayConverter implements Converter<String, char[]> {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DurationFormat {
@Override
public char[] convert(String source) {
return source.toCharArray();
}
/**
* The duration format style.
* @return the duration format style.
*/
DurationStyle value();
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Duration format styles.
*
* @author Phillip Webb
* @since 2.0.0
*/
public enum DurationStyle {
/**
* Simple formatting, for example '1s'.
*/
SIMPLE("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$") {
@Override
public Duration parse(String value, ChronoUnit unit) {
try {
Matcher matcher = matcher(value);
Assert.state(matcher.matches(), "Does not match simple duration pattern");
String suffix = matcher.group(2);
return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix)
: Unit.fromChronoUnit(unit)).parse(matcher.group(1));
}
catch (Exception ex) {
throw new IllegalArgumentException(
"'" + value + "' is not a valid simple duration", ex);
}
}
@Override
public String print(Duration value, ChronoUnit unit) {
return Unit.fromChronoUnit(unit).print(value);
}
},
/**
* ISO-8601 formatting.
*/
ISO8601("^[\\+\\-]?P.*$") {
@Override
public Duration parse(String value, ChronoUnit unit) {
try {
return Duration.parse(value);
}
catch (Exception ex) {
throw new IllegalArgumentException(
"'" + value + "' is not a valid ISO-8601 duration", ex);
}
}
@Override
public String print(Duration value, ChronoUnit unit) {
return value.toString();
}
};
private final Pattern pattern;
DurationStyle(String pattern) {
this.pattern = Pattern.compile(pattern);
}
protected final boolean matches(String value) {
return this.pattern.matcher(value).matches();
}
protected final Matcher matcher(String value) {
return this.pattern.matcher(value);
}
/**
* Parse the given value to a duration.
* @param value the value to parse
* @return a duration
*/
public Duration parse(String value) {
return parse(value, null);
}
/**
* Parse the given value to a duration.
* @param value the value to parse
* @param unit the duration unit to use if the value doesn't specify one ({@code null}
* will default to ms)
* @return a duration
*/
public abstract Duration parse(String value, ChronoUnit unit);
/**
* Print the specified duration.
* @param value the value to print
* @return the printed result
*/
public String print(Duration value) {
return print(value, null);
}
/**
* Print the specified duration using the given unit.
* @param value the value to print
* @param unit the value to use for printing
* @return the printed result
*/
public abstract String print(Duration value, ChronoUnit unit);
/**
* Detect the style then parse the value to return a duration.
* @param value the value to parse
* @return the parsed duration
* @throws IllegalStateException if the value is not a known style or cannot be parsed
*/
public static Duration detectAndParse(String value) {
return detectAndParse(value, null);
}
/**
* Detect the style then parse the value to return a duration.
* @param value the value to parse
* @param unit the duration unit to use if the value doesn't specify one ({@code null}
* will default to ms)
* @return the parsed duration
* @throws IllegalStateException if the value is not a known style or cannot be parsed
*/
public static Duration detectAndParse(String value, ChronoUnit unit) {
return detect(value).parse(value, unit);
}
/**
* Detect the style from the given source value.
* @param value the source value
* @return the duration style
* @throws IllegalStateException if the value is not a known style
*/
public static DurationStyle detect(String value) {
Assert.notNull(value, "Value must not be null");
for (DurationStyle candidate : values()) {
if (candidate.matches(value)) {
return candidate;
}
}
throw new IllegalArgumentException("'" + value + "' is not a valid duration");
}
/**
* Units that we support.
*/
enum Unit {
/**
* Nanoseconds.
*/
NANOS(ChronoUnit.NANOS, "ns", Duration::toNanos),
/**
* Milliseconds.
*/
MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),
/**
* Seconds.
*/
SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),
/**
* Minutes.
*/
MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),
/**
* Hours.
*/
HOURS(ChronoUnit.HOURS, "h", Duration::toHours),
/**
* Days.
*/
DAYS(ChronoUnit.DAYS, "d", Duration::toDays);
private final ChronoUnit chronoUnit;
private final String suffix;
private Function<Duration, Long> longValue;
Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) {
this.chronoUnit = chronoUnit;
this.suffix = suffix;
this.longValue = toUnit;
}
public Duration parse(String value) {
return Duration.of(Long.valueOf(value), this.chronoUnit);
}
public String print(Duration value) {
return longValue(value) + this.suffix;
}
public long longValue(Duration value) {
return this.longValue.apply(value);
}
public static Unit fromChronoUnit(ChronoUnit chronoUnit) {
if (chronoUnit == null) {
return Unit.MILLIS;
}
for (Unit candidate : values()) {
if (candidate.chronoUnit == chronoUnit) {
return candidate;
}
}
throw new IllegalArgumentException("Unknown unit " + chronoUnit);
}
public static Unit fromSuffix(String suffix) {
for (Unit candidate : values()) {
if (candidate.suffix.equalsIgnoreCase(suffix)) {
return candidate;
}
}
throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.ReflectionUtils;
/**
* {@link Converter} to convert from a {@link Duration} to a {@link Number}.
*
* @author Phillip Webb
* @see DurationFormat
* @see DurationUnit
*/
final class DurationToNumberConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Duration.class, Number.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
DurationUnit unit = sourceType.getAnnotation(DurationUnit.class);
return convert((Duration) source, (unit == null ? null : unit.value()),
targetType.getObjectType());
}
private Object convert(Duration source, ChronoUnit unit, Class<?> type) {
try {
return type.getConstructor(String.class).newInstance(String
.valueOf(DurationStyle.Unit.fromChronoUnit(unit).longValue(source)));
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
throw new IllegalStateException(ex);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* {@link Converter} to convert from a {@link Duration} to a {@link String}.
*
* @author Phillip Webb
* @see DurationFormat
* @see DurationUnit
*/
final class DurationToStringConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Duration.class, String.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
DurationFormat format = sourceType.getAnnotation(DurationFormat.class);
DurationUnit unit = sourceType.getAnnotation(DurationUnit.class);
return convert((Duration) source, (format == null ? null : format.value()),
(unit == null ? null : unit.value()));
}
private String convert(Duration source, DurationStyle style, ChronoUnit unit) {
style = (style != null ? style : DurationStyle.ISO8601);
return style.print(source, unit);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -34,7 +34,7 @@ import java.time.temporal.ChronoUnit;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefaultDurationUnit {
public @interface DurationUnit {
/**
* The duration unit to use if one is not specified.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@@ -14,29 +14,34 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.beans.PropertyEditor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.Locale;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter;
/**
* {@link PropertyEditor} for {@link InetAddress} objects.
* {@link Formatter} for {@link InetAddress}.
*
* @author Dave Syer
* @author Phillip Webb
*/
class StringToInetAddressConverter implements Converter<String, InetAddress> {
final class InetAddressFormatter implements Formatter<InetAddress> {
@Override
public InetAddress convert(String source) {
public String print(InetAddress object, Locale locale) {
return object.getHostAddress();
}
@Override
public InetAddress parse(String text, Locale locale) throws ParseException {
try {
return InetAddress.getByName(source);
return InetAddress.getByName(text);
}
catch (UnknownHostException ex) {
throw new IllegalStateException("Unknown host " + source, ex);
throw new IllegalStateException("Unknown host " + text, ex);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.text.ParseException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import org.springframework.format.Formatter;
/**
* A {@link Formatter} for {@link OffsetDateTime} that uses
* {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO offset formatting}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
class IsoOffsetFormatter implements Formatter<OffsetDateTime> {
@Override
public String print(OffsetDateTime object, Locale locale) {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(object);
}
@Override
public OffsetDateTime parse(String text, Locale locale) throws ParseException {
return OffsetDateTime.parse(text, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* {@link Converter} to convert from a {@link Number} to a {@link Duration}. Supports
* {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form.
*
* @author Phillip Webb
* @see DurationFormat
* @see DurationUnit
*/
final class NumberToDurationConverter implements GenericConverter {
private final StringToDurationConverter delegate = new StringToDurationConverter();
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Number.class, Duration.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return this.delegate.convert(source == null ? null : source.toString(),
TypeDescriptor.valueOf(String.class), targetType);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.ObjectUtils;
/**
* {@link Converter} to convert from a {@link String} to a {@link Duration}. Supports
* {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form.
*
* @author Phillip Webb
* @see DurationFormat
* @see DurationUnit
*/
final class StringToDurationConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Duration.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (ObjectUtils.isEmpty(source)) {
return null;
}
DurationFormat format = targetType.getAnnotation(DurationFormat.class);
DurationUnit unit = targetType.getAnnotation(DurationUnit.class);
return convert(source.toString(), (format == null ? null : format.value()),
(unit == null ? null : unit.value()));
}
private Duration convert(String source, DurationStyle style, ChronoUnit unit) {
style = (style != null ? style : DurationStyle.detect(source));
return style.parse(source, unit);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.util.EnumSet;
import java.util.Set;
@@ -30,7 +30,8 @@ import org.springframework.util.Assert;
* @author Phillip Webb
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
final class StringToEnumIgnoringCaseConverterFactory
implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {

View File

@@ -24,7 +24,7 @@ import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
/**
* Subset of Narayana properties which can be configured via Spring configuration. Use
@@ -59,21 +59,21 @@ public class NarayanaProperties {
/**
* Transaction timeout. If a duration suffix is not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration defaultTimeout = Duration.ofSeconds(60);
/**
* Interval in which periodic recovery scans are performed. If a duration suffix is
* not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration periodicRecoveryPeriod = Duration.ofSeconds(120);
/**
* Back off period between first and second phases of the recovery scan. If a duration
* suffix is not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration recoveryBackoffPeriod = Duration.ofSeconds(10);
/**

View File

@@ -21,7 +21,7 @@ import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Set;
import org.springframework.boot.context.properties.bind.convert.DefaultDurationUnit;
import org.springframework.boot.convert.DurationUnit;
/**
* Session properties.
@@ -34,7 +34,7 @@ public class Session {
/**
* Session timeout. If a duration suffix is not specified, seconds will be used.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration timeout = Duration.ofMinutes(30);
/**
@@ -135,7 +135,7 @@ public class Session {
/**
* Maximum age of the session cookie.
*/
@DefaultDurationUnit(ChronoUnit.SECONDS)
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge;
public String getName() {

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.context.properties;
import java.beans.PropertyEditorSupport;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
@@ -675,6 +676,16 @@ public class ConfigurationPropertiesTests {
load(IgnoreUnknownFieldsFalseConfiguration.class, "name=foo", "bar=baz");
}
@Test
public void loadWhenHasCustomPropertyEditorShouldBind() {
this.context.getBeanFactory().registerCustomEditor(Person.class,
PersonPropertyEditor.class);
load(PersonProperties.class, "test.person=boot,spring");
PersonProperties bean = this.context.getBean(PersonProperties.class);
assertThat(bean.getPerson().firstName).isEqualTo("spring");
assertThat(bean.getPerson().lastName).isEqualTo("boot");
}
private AnnotationConfigApplicationContext load(Class<?> configuration,
String... inlinedProperties) {
return load(new Class<?>[] { configuration }, inlinedProperties);
@@ -1556,6 +1567,16 @@ public class ConfigurationPropertiesTests {
}
}
static class PersonPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] split = text.split(",");
setValue(new Person(split[1], split[0]));
}
}
static class Person {
private final String firstName;

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.context.properties.bind;
import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
@@ -160,6 +161,16 @@ public class BinderTests {
this.binder.bind("foo", Bindable.of(Integer.class));
}
@Test
public void bindToValueWithCustomPropertyEditorShouldReturnConvertedValue() {
this.binder = new Binder(this.sources, null, null, (registry) -> registry
.registerCustomEditor(JavaBean.class, new JavaBeanPropertyEditor()));
this.sources.add(new MockConfigurationPropertySource("foo", "123"));
BindResult<JavaBean> result = this.binder.bind("foo",
Bindable.of(JavaBean.class));
assertThat(result.get().getValue()).isEqualTo("123");
}
@Test
public void bindToValueShouldTriggerOnSuccess() {
this.sources.add(new MockConfigurationPropertySource("foo", "1", "line1"));
@@ -280,8 +291,8 @@ public class BinderTests {
this.binder.bind("foo", target);
}
@SuppressWarnings("rawtypes")
@Test
@SuppressWarnings("rawtypes")
public void bindToBeanWithUnresolvableGenerics() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "hello");
@@ -373,4 +384,15 @@ public class BinderTests {
}
public static class JavaBeanPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
JavaBean value = new JavaBean();
value.setValue(text);
setValue(value);
}
}
}

View File

@@ -31,11 +31,11 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.context.properties.bind.convert.Delimiter;
import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import org.springframework.boot.convert.Delimiter;
import org.springframework.format.annotation.DateTimeFormat;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -536,7 +536,7 @@ public class MapBinderTests {
public void bindToMapWithCustomConverter() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MapConverter());
Binder binder = new Binder(this.sources, null, conversionService);
Binder binder = new Binder(this.sources, null, conversionService, null);
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo", "a,b");
this.sources.add(source);
@@ -550,7 +550,7 @@ public class MapBinderTests {
// gh-11892
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MapConverter());
Binder binder = new Binder(this.sources, null, conversionService);
Binder binder = new Binder(this.sources, null, conversionService, null);
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo", "boom");
source.put("foo.a", "a");

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import java.util.List;
import org.junit.Test;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.TypeDescriptor;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ResolvableTypeDescriptor}.
*
* @author Phillip Webb
*/
public class ResolvableTypeDescriptorTests {
@Test
public void forBindableShouldIncludeType() {
ResolvableType type = ResolvableType.forClassWithGenerics(List.class,
String.class);
Bindable<?> bindable = Bindable.of(type);
TypeDescriptor descriptor = ResolvableTypeDescriptor.forBindable(bindable);
assertThat(descriptor.getResolvableType()).isEqualTo(type);
}
@Test
public void forBindableShouldIncludeAnnotations() {
Annotation annotation = AnnotationUtils.synthesizeAnnotation(Test.class);
Bindable<?> bindable = Bindable.of(String.class).withAnnotations(annotation);
TypeDescriptor descriptor = ResolvableTypeDescriptor.forBindable(bindable);
assertThat(descriptor.getAnnotations()).containsExactly(annotation);
}
@Test
public void forTypeShouldIncludeType() {
ResolvableType type = ResolvableType.forClassWithGenerics(List.class,
String.class);
TypeDescriptor descriptor = ResolvableTypeDescriptor.forType(type);
assertThat(descriptor.getResolvableType()).isEqualTo(type);
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.AssumptionViolatedException;
/**
* Base class for {@link InetAddress} tests.
*
* @author Phillip Webb
*/
public abstract class AbstractInetAddressTests {
public void assumeResolves(String host) {
try {
InetAddress.getByName(host);
}
catch (UnknownHostException ex) {
throw new AssumptionViolatedException("Host " + host + " not resolvable", ex);
}
}
}

View File

@@ -1,207 +0,0 @@
/*
* Copyright 2012-2018 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.boot.context.properties.bind.convert;
import java.io.InputStream;
import java.net.InetAddress;
import java.time.Duration;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.Resource;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link BinderConversionService}.
*
* @author Phillip Webb
*/
public class BinderConversionServiceTests {
private ConversionService delegate;
private BinderConversionService service;
@Before
public void setup() {
this.delegate = mock(ConversionService.class);
this.service = new BinderConversionService(this.delegate);
}
@Test
public void createConversionServiceShouldAcceptNullConversionService() {
BinderConversionService service = new BinderConversionService(null);
assertThat(service.canConvert(String.class, TestEnum.class)).isTrue();
assertThat(service.canConvert(TypeDescriptor.valueOf(String.class),
TypeDescriptor.valueOf(TestEnum.class))).isTrue();
assertThat(service.convert("ONE", TestEnum.class)).isEqualTo(TestEnum.ONE);
assertThat(service.convert("ONE", TypeDescriptor.valueOf(String.class),
TypeDescriptor.valueOf(TestEnum.class))).isEqualTo(TestEnum.ONE);
}
@Test
public void canConvertShouldDelegateToConversionService() {
Class<String> from = String.class;
Class<InputStream> to = InputStream.class;
given(this.delegate.canConvert(from, to)).willReturn(true);
assertThat(this.service.canConvert(from, to)).isEqualTo(true);
verify(this.delegate).canConvert(from, to);
}
@Test
public void canConvertTypeDescriptorShouldDelegateToConversionService() {
TypeDescriptor from = TypeDescriptor.valueOf(String.class);
TypeDescriptor to = TypeDescriptor.valueOf(InputStream.class);
given(this.delegate.canConvert(from, to)).willReturn(true);
assertThat(this.service.canConvert(from, to)).isEqualTo(true);
verify(this.delegate).canConvert(from, to);
}
@Test
public void convertShouldDelegateToConversionService() {
String from = "foo";
InputStream to = mock(InputStream.class);
given(this.delegate.convert(from, InputStream.class)).willReturn(to);
assertThat(this.service.convert(from, InputStream.class)).isEqualTo(to);
verify(this.delegate).convert(from, InputStream.class);
}
@Test
public void convertTargetTypeShouldDelegateToConversionService() {
String from = "foo";
InputStream to = mock(InputStream.class);
TypeDescriptor fromType = TypeDescriptor.valueOf(String.class);
TypeDescriptor toType = TypeDescriptor.valueOf(InputStream.class);
given(this.delegate.convert(from, fromType, toType)).willReturn(to);
assertThat(this.service.convert(from, fromType, toType)).isEqualTo(to);
verify(this.delegate).convert(from, fromType, toType);
}
@Test
public void convertShouldSwallowDelegateConversionFailedException() {
given(this.delegate.convert("one", TestEnum.class))
.willThrow(new ConversionFailedException(null, null, null, null));
assertThat(this.service.convert("one", TestEnum.class)).isEqualTo(TestEnum.ONE);
verify(this.delegate).convert("one", TestEnum.class);
}
@Test
public void conversionServiceShouldSupportEnums() {
this.service = new BinderConversionService(null);
assertThat(this.service.canConvert(String.class, TestEnum.class)).isTrue();
assertThat(this.service.convert("one", TestEnum.class)).isEqualTo(TestEnum.ONE);
assertThat(this.service.convert("t-w-o", TestEnum.class)).isEqualTo(TestEnum.TWO);
}
@Test
public void conversionServiceShouldSupportStringToCharArray() {
this.service = new BinderConversionService(null);
assertThat(this.service.canConvert(String.class, char[].class)).isTrue();
assertThat(this.service.convert("test", char[].class)).containsExactly('t', 'e',
's', 't');
}
@Test
public void conversionServiceShouldSupportStringToInetAddress() {
this.service = new BinderConversionService(null);
assertThat(this.service.canConvert(String.class, InetAddress.class)).isTrue();
}
@Test
public void conversionServiceShouldSupportInetAddressToString() {
this.service = new BinderConversionService(null);
assertThat(this.service.canConvert(InetAddress.class, String.class)).isTrue();
}
@Test
public void conversionServiceShouldSupportStringToResource() {
this.service = new BinderConversionService(null);
Resource resource = this.service.convert(
"org/springframework/boot/context/properties/bind/convert/resource.txt",
Resource.class);
assertThat(resource).isNotNull();
}
@Test
public void conversionServiceShouldSupportStringToClass() {
this.service = new BinderConversionService(null);
Class<?> converted = this.service.convert(InputStream.class.getName(),
Class.class);
assertThat(converted).isEqualTo(InputStream.class);
}
@Test
public void conversionServiceShouldSupportStringToDuration() {
this.service = new BinderConversionService(null);
Duration converted = this.service.convert("10s", Duration.class);
assertThat(converted).isEqualTo(Duration.ofSeconds(10));
}
@Test
public void conversionServiceShouldSupportIntegerToDuration() {
this.service = new BinderConversionService(null);
Duration converted = this.service.convert(10, Duration.class);
assertThat(converted).isEqualTo(Duration.ofMillis(10));
}
@Test
@SuppressWarnings("unchecked")
public void conversionServiceShouldSupportBarDelimitedStrings() {
this.service = new BinderConversionService(null);
List<TestEnum> converted = (List<TestEnum>) this.service.convert("ONE|ONE|TWO",
TypeDescriptor.valueOf(String.class), TypeDescriptor.nested(
ReflectionUtils.findField(DelimitedValues.class, "bar"), 0));
assertThat(converted).containsExactly(TestEnum.ONE, TestEnum.ONE, TestEnum.TWO);
}
@Test
@SuppressWarnings("unchecked")
public void conversionServiceShouldSupportNoneDelimitedStrings() {
this.service = new BinderConversionService(null);
List<String> converted = (List<String>) this.service.convert("a,b,c",
TypeDescriptor.valueOf(String.class), TypeDescriptor.nested(
ReflectionUtils.findField(DelimitedValues.class, "none"), 0));
assertThat(converted).containsExactly("a,b,c");
}
static class DelimitedValues {
@Delimiter("|")
List<TestEnum> bar;
@Delimiter(Delimiter.NONE)
List<String> none;
}
enum TestEnum {
ONE, TWO
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import java.net.InetAddress;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link InetAddressToStringConverter}.
*
* @author Phillip Webb
*/
public class InetAddressToStringConverterTests extends AbstractInetAddressTests {
private InetAddressToStringConverter converter = new InetAddressToStringConverter();
@Test
public void convertShouldConvertToHostAddress() throws Exception {
assumeResolves("example.com");
InetAddress address = InetAddress.getByName("example.com");
String converted = this.converter.convert(address);
assertThat(converted).isEqualTo(address.getHostAddress());
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import org.junit.Test;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PropertyEditorConverter}.
*
* @author Phillip Webb
*/
public class PropertyEditorConverterTests {
private final PropertyEditorConverter converter = new PropertyEditorConverter();
@Test
public void matchesShouldLimitToPropertyEditor() {
String converted = new SimpleTypeConverter().convertIfNecessary(123,
String.class);
assertThat(converted).isEqualTo("123");
// Even though the SimpleTypeConverter can convert, we should limit to just
// PropertyEditors not implicit support
assertThat(this.converter.matches(TypeDescriptor.valueOf(Integer.class),
TypeDescriptor.valueOf(String.class))).isFalse();
}
@Test
public void convertShouldSupportConventionBasedEditors() {
String source = "org/springframework/boot/context/properties/bind/convert/resource.txt";
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
TypeDescriptor targetType = TypeDescriptor.valueOf(Resource.class);
assertThat(this.converter.matches(sourceType, targetType)).isTrue();
Object converted = this.converter.convert(source, sourceType, targetType);
assertThat(converted).isNotNull().isInstanceOf(Resource.class);
assertThat(converted.toString()).endsWith("resource.txt]");
}
@Test
public void convertShouldSupportDefaultEditors() {
String source = "en_UK";
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
TypeDescriptor targetType = TypeDescriptor.valueOf(Locale.class);
assertThat(this.converter.matches(sourceType, targetType)).isTrue();
Object converted = this.converter.convert(source, sourceType, targetType);
assertThat(converted).isNotNull().isInstanceOf(Locale.class);
assertThat(converted.toString()).endsWith("en_UK");
}
@Test
public void matchShouldNotMatchCollection() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
assertThat(this.converter.matches(sourceType,
TypeDescriptor.valueOf(Collection.class))).isFalse();
assertThat(this.converter.matches(sourceType, TypeDescriptor.valueOf(List.class)))
.isFalse();
assertThat(this.converter.matches(sourceType, TypeDescriptor.valueOf(Set.class)))
.isFalse();
}
@Test
public void matchShouldNotMatchMap() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
assertThat(this.converter.matches(sourceType, TypeDescriptor.valueOf(Map.class)))
.isFalse();
assertThat(this.converter.matches(sourceType,
TypeDescriptor.valueOf(SortedMap.class))).isFalse();
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link StringToCharArrayConverter}.
*
* @author Phillip Webb
*/
public class StringToCharArrayConverterTests {
private StringToCharArrayConverter converter = new StringToCharArrayConverter();
@Test
public void convertShouldConvertSource() {
char[] converted = this.converter.convert("test");
assertThat(converted).containsExactly('t', 'e', 's', 't');
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import org.junit.Test;
import org.springframework.core.convert.converter.Converter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link StringToEnumConverterFactory}.
*
* @author Phillip Webb
*/
public class StringToEnumConverterFactoryTests {
private StringToEnumConverterFactory factory = new StringToEnumConverterFactory();
@Test
public void getConverterShouldReturnConverter() {
Converter<String, TestEnum> converter = this.factory.getConverter(TestEnum.class);
assertThat(converter).isNotNull();
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void getConverterWhenEnumSubclassShouldReturnConverter() {
Converter<String, TestEnum> converter = this.factory
.getConverter((Class) TestSubclassEnum.ONE.getClass());
assertThat(converter).isNotNull();
}
@Test
public void convertWhenExactMatchShouldConvertValue() {
Converter<String, TestEnum> converter = this.factory.getConverter(TestEnum.class);
assertThat(converter.convert("")).isNull();
assertThat(converter.convert("ONE")).isEqualTo(TestEnum.ONE);
assertThat(converter.convert("TWO")).isEqualTo(TestEnum.TWO);
assertThat(converter.convert("THREE_AND_FOUR"))
.isEqualTo(TestEnum.THREE_AND_FOUR);
}
@Test
public void convertWhenFuzzyMatchShouldConvertValue() {
Converter<String, TestEnum> converter = this.factory.getConverter(TestEnum.class);
assertThat(converter.convert("")).isNull();
assertThat(converter.convert("one")).isEqualTo(TestEnum.ONE);
assertThat(converter.convert("tWo")).isEqualTo(TestEnum.TWO);
assertThat(converter.convert("three_and_four"))
.isEqualTo(TestEnum.THREE_AND_FOUR);
assertThat(converter.convert("threeandfour")).isEqualTo(TestEnum.THREE_AND_FOUR);
assertThat(converter.convert("three-and-four"))
.isEqualTo(TestEnum.THREE_AND_FOUR);
assertThat(converter.convert("threeAndFour")).isEqualTo(TestEnum.THREE_AND_FOUR);
}
enum TestEnum {
ONE, TWO, THREE_AND_FOUR
}
enum TestSubclassEnum {
ONE {
@Override
public String toString() {
return "foo";
}
}
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2012-2017 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.boot.context.properties.bind.convert;
import java.net.InetAddress;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link StringToInetAddressConverter}.
*
* @author Phillip Webb
*/
public class StringToInetAddressConverterTests extends AbstractInetAddressTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private StringToInetAddressConverter converter = new StringToInetAddressConverter();
@Test
public void convertWhenHostDoesNotExistShouldThrowException() {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Unknown host");
this.converter.convert("ireallydontexist.example.com");
}
@Test
public void convertWhenHostExistsShouldConvert() {
assumeResolves("example.com");
InetAddress converted = this.converter.convert("example.com");
assertThat(converted.toString()).startsWith("example.com");
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2012-2018 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.boot.convert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ArrayToDelimitedStringConverter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class ArrayToDelimitedStringConverterTests {
private final ConversionService conversionService;
public ArrayToDelimitedStringConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertListToStringShouldConvert() {
String[] list = { "a", "b", "c" };
String converted = this.conversionService.convert(list, String.class);
assertThat(converted).isEqualTo("a,b,c");
}
@Test
public void convertWhenHasDelimiterNoneShouldConvert() {
Data data = new Data();
data.none = new String[] { "1", "2", "3" };
String converted = (String) this.conversionService.convert(data.none,
TypeDescriptor.nested(ReflectionUtils.findField(Data.class, "none"), 0),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("123");
}
@Test
public void convertWhenHasDelimiterDashShouldConvert() {
Data data = new Data();
data.dash = new String[] { "1", "2", "3" };
String converted = (String) this.conversionService.convert(data.dash,
TypeDescriptor.nested(ReflectionUtils.findField(Data.class, "dash"), 0),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("1-2-3");
}
@Test
public void convertShouldConvertElements() {
if (this.conversionService instanceof ApplicationConversionService) {
Data data = new Data();
data.type = new int[] { 1, 2, 3 };
String converted = (String) this.conversionService.convert(
data.type, TypeDescriptor
.nested(ReflectionUtils.findField(Data.class, "type"), 0),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("1.2.3");
}
}
@Test
public void convertShouldConvertNull() {
String[] list = null;
String converted = this.conversionService.convert(list, String.class);
assertThat(converted).isNull();
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(
ArrayToDelimitedStringConverterTests::addConverter);
}
private static void addConverter(FormattingConversionService service) {
service.addConverter(new ArrayToDelimitedStringConverter(service));
}
static class Data {
@Delimiter(Delimiter.NONE)
String[] none;
@Delimiter("-")
String[] dash;
@Delimiter(".")
int[] type;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2018 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.boot.convert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CharArrayFormatter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class CharArrayFormatterTests {
private final ConversionService conversionService;
public CharArrayFormatterTests(String name, ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertFromCharArrayToStringShouldConvert() {
char[] source = { 'b', 'o', 'o', 't' };
String converted = this.conversionService.convert(source, String.class);
assertThat(converted).isEqualTo("boot");
}
@Test
public void convertFromStringToCharArrayShouldConvert() {
String source = "boot";
char[] converted = this.conversionService.convert(source, char[].class);
assertThat(converted).containsExactly('b', 'o', 'o', 't');
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new CharArrayFormatter());
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CollectionToDelimitedStringConverter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class CollectionToDelimitedStringConverterTests {
private final ConversionService conversionService;
public CollectionToDelimitedStringConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertListToStringShouldConvert() {
List<String> list = Arrays.asList("a", "b", "c");
String converted = this.conversionService.convert(list, String.class);
assertThat(converted).isEqualTo("a,b,c");
}
@Test
public void convertWhenHasDelimiterNoneShouldConvert() {
Data data = new Data();
data.none = Arrays.asList("1", "2", "3");
String converted = (String) this.conversionService.convert(data.none,
TypeDescriptor.nested(ReflectionUtils.findField(Data.class, "none"), 0),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("123");
}
@Test
public void convertWhenHasDelimiterDashShouldConvert() {
Data data = new Data();
data.dash = Arrays.asList("1", "2", "3");
String converted = (String) this.conversionService.convert(data.dash,
TypeDescriptor.nested(ReflectionUtils.findField(Data.class, "dash"), 0),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("1-2-3");
}
@Test
public void convertShouldConvertElements() {
if (this.conversionService instanceof ApplicationConversionService) {
Data data = new Data();
data.type = Arrays.asList(1, 2, 3);
String converted = (String) this.conversionService.convert(
data.type, TypeDescriptor
.nested(ReflectionUtils.findField(Data.class, "type"), 0),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("1.2.3");
}
}
@Test
public void convertShouldConvertNull() {
List<String> list = null;
String converted = this.conversionService.convert(list, String.class);
assertThat(converted).isNull();
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(
CollectionToDelimitedStringConverterTests::addConverter);
}
private static void addConverter(FormattingConversionService service) {
service.addConverter(new CollectionToDelimitedStringConverter(service));
}
static class Data {
@Delimiter(Delimiter.NONE)
List<String> none;
@Delimiter("-")
List<String> dash;
@Delimiter(".")
List<Integer> type;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.format.Formatter;
import org.springframework.format.support.FormattingConversionService;
/**
* Factory to create {@link ConversionService ConversionServices} for test
* {@link Parameters}.
*
* @author Phillip Webb
*/
public class ConversionServiceParameters implements Iterable<Object[]> {
private final List<Object[]> parameters;
public ConversionServiceParameters(Formatter<?> formatter) {
this((Consumer<FormattingConversionService>) (
conversionService) -> conversionService.addFormatter(formatter));
}
public ConversionServiceParameters(ConverterFactory<?, ?> converterFactory) {
this((Consumer<FormattingConversionService>) (
conversionService) -> conversionService
.addConverterFactory(converterFactory));
}
public ConversionServiceParameters(GenericConverter converter) {
this((Consumer<FormattingConversionService>) (
conversionService) -> conversionService.addConverter(converter));
}
public ConversionServiceParameters(
Consumer<FormattingConversionService> initializer) {
FormattingConversionService withoutDefaults = new FormattingConversionService();
initializer.accept(withoutDefaults);
List<Object[]> parameters = new ArrayList<>();
parameters.add(
new Object[] { "without defaults conversion service", withoutDefaults });
parameters.add(new Object[] { "application conversion service",
new ApplicationConversionService() });
this.parameters = Collections.unmodifiableList(parameters);
}
@Override
public Iterator<Object[]> iterator() {
return this.parameters.iterator();
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.util.LinkedList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DelimitedStringToArrayConverter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class DelimitedStringToArrayConverterTests {
private final ConversionService conversionService;
public DelimitedStringToArrayConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void canConvertFromStringToArrayShouldReturnTrue() {
assertThat(this.conversionService.canConvert(String.class, String[].class))
.isTrue();
}
@Test
public void matchesWhenTargetIsNotAnnotatedShouldReturnTrue() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "noAnnotation"), 0);
assertThat(new DelimitedStringToArrayConverter(this.conversionService)
.matches(sourceType, targetType)).isTrue();
}
@Test
public void matchesWhenHasAnnotationAndConvertibleElementTypeShouldReturnTrue() {
if (this.conversionService instanceof ApplicationConversionService) {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "convertibleElementType"), 0);
assertThat(new DelimitedStringToArrayConverter(this.conversionService)
.matches(sourceType, targetType)).isTrue();
}
}
@Test
public void matchesWhenHasAnnotationAndNonConvertibleElementTypeShouldReturnFalse() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "nonConvertibleElementType"), 0);
assertThat(new DelimitedStringToArrayConverter(this.conversionService)
.matches(sourceType, targetType)).isFalse();
}
@Test
public void convertWhenHasConvertibleElementTypeShouldReturnConvertedType() {
if (this.conversionService instanceof ApplicationConversionService) {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "convertibleElementType"), 0);
Integer[] converted = (Integer[]) this.conversionService
.convert(" 1 | 2| 3 ", sourceType, targetType);
assertThat(converted).containsExactly(1, 2, 3);
}
}
@Test
public void convertWhenHasDelimiterOfNoneShouldReturnTrimmedStringElement() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "delimiterNone"), 0);
String[] converted = (String[]) this.conversionService.convert("a,b,c",
sourceType, targetType);
assertThat(converted).containsExactly("a,b,c");
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(
DelimitedStringToArrayConverterTests::addConverter);
}
private static void addConverter(FormattingConversionService service) {
service.addConverter(new DelimitedStringToArrayConverter(service));
}
static class Values {
List<String> noAnnotation;
@Delimiter("|")
Integer[] convertibleElementType;
@Delimiter("|")
NonConvertible[] nonConvertibleElementType;
@Delimiter(Delimiter.NONE)
String[] delimiterNone;
}
static class NonConvertible {
}
static class MyCustomList<E> extends LinkedList<E> {
}
}

View File

@@ -14,21 +14,22 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -38,45 +39,32 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class DelimitedStringToCollectionConverterTests {
private DefaultFormattingConversionService service;
private final ConversionService conversionService;
private DelimitedStringToCollectionConverter converter;
public DelimitedStringToCollectionConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setup() {
this.service = new DefaultFormattingConversionService(null, false);
this.converter = new DelimitedStringToCollectionConverter(this.service);
this.service.addConverter(this.converter);
DefaultFormattingConversionService.addDefaultFormatters(this.service);
@Test
public void canConvertFromStringToCollectionShouldReturnTrue() {
assertThat(this.conversionService.canConvert(String.class, Collection.class))
.isTrue();
}
@Test
public void createWhenConversionServiceIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ConversionService must not be null");
new DelimitedStringToCollectionConverter(null);
}
@Test
public void getConvertiblePairShouldReturnStringCollectionPair() {
Set<ConvertiblePair> types = this.converter.getConvertibleTypes();
assertThat(types).hasSize(1);
ConvertiblePair pair = types.iterator().next();
assertThat(pair.getSourceType()).isEqualTo(String.class);
assertThat(pair.getTargetType()).isEqualTo(Collection.class);
}
@Test
public void matchesWhenTargetIsNotAnnotatedShouldReturnFalse() {
public void matchesWhenTargetIsNotAnnotatedShouldReturnTrue() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "noAnnotation"), 0);
assertThat(this.converter.matches(sourceType, targetType)).isFalse();
assertThat(new DelimitedStringToCollectionConverter(this.conversionService)
.matches(sourceType, targetType)).isTrue();
}
@Test
@@ -84,15 +72,19 @@ public class DelimitedStringToCollectionConverterTests {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "noElementType"), 0);
assertThat(this.converter.matches(sourceType, targetType)).isTrue();
assertThat(new DelimitedStringToCollectionConverter(this.conversionService)
.matches(sourceType, targetType)).isTrue();
}
@Test
public void matchesWhenHasAnnotationAndConvertibleElementTypeShouldReturnTrue() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "convertibleElementType"), 0);
assertThat(this.converter.matches(sourceType, targetType)).isTrue();
if (this.conversionService instanceof ApplicationConversionService) {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "convertibleElementType"), 0);
assertThat(new DelimitedStringToCollectionConverter(this.conversionService)
.matches(sourceType, targetType)).isTrue();
}
}
@Test
@@ -100,7 +92,8 @@ public class DelimitedStringToCollectionConverterTests {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "nonConvertibleElementType"), 0);
assertThat(this.converter.matches(sourceType, targetType)).isFalse();
assertThat(new DelimitedStringToCollectionConverter(this.conversionService)
.matches(sourceType, targetType)).isFalse();
}
@Test
@@ -109,20 +102,22 @@ public class DelimitedStringToCollectionConverterTests {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "noElementType"), 0);
List<String> converted = (List<String>) this.converter.convert(" a | b| c ",
sourceType, targetType);
Collection<String> converted = (Collection<String>) this.conversionService
.convert(" a | b| c ", sourceType, targetType);
assertThat(converted).containsExactly("a", "b", "c");
}
@Test
@SuppressWarnings("unchecked")
public void convertWhenHasConvertibleElementTypeShouldReturnConvertedType() {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "convertibleElementType"), 0);
List<Integer> converted = (List<Integer>) this.converter.convert(" 1 | 2| 3 ",
sourceType, targetType);
assertThat(converted).containsExactly(1, 2, 3);
if (this.conversionService instanceof ApplicationConversionService) {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor.nested(
ReflectionUtils.findField(Values.class, "convertibleElementType"), 0);
List<Integer> converted = (List<Integer>) this.conversionService
.convert(" 1 | 2| 3 ", sourceType, targetType);
assertThat(converted).containsExactly(1, 2, 3);
}
}
@Test
@@ -131,7 +126,7 @@ public class DelimitedStringToCollectionConverterTests {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "delimiterNone"), 0);
List<String> converted = (List<String>) this.converter.convert("a,b,c",
List<String> converted = (List<String>) this.conversionService.convert("a,b,c",
sourceType, targetType);
assertThat(converted).containsExactly("a,b,c");
}
@@ -141,10 +136,20 @@ public class DelimitedStringToCollectionConverterTests {
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
TypeDescriptor targetType = TypeDescriptor
.nested(ReflectionUtils.findField(Values.class, "specificType"), 0);
Object converted = this.converter.convert("a*b", sourceType, targetType);
Object converted = this.conversionService.convert("a*b", sourceType, targetType);
assertThat(converted).isInstanceOf(MyCustomList.class);
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(
DelimitedStringToCollectionConverterTests::addConverter);
}
private static void addConverter(FormattingConversionService service) {
service.addConverter(new DelimitedStringToCollectionConverter(service));
}
static class Values {
List<String> noAnnotation;

View File

@@ -0,0 +1,281 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
/**
* Tests for {@link DurationStyle}.
*
* @author Phillip Webb
*/
public class DurationStyleTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void detectAndParseWhenValueIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Value must not be null");
DurationStyle.detectAndParse(null);
}
@Test
public void detectAndParseWhenIso8601ShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("PT20.345S"))
.isEqualTo(Duration.parse("PT20.345S"));
assertThat(DurationStyle.detectAndParse("PT15M"))
.isEqualTo(Duration.parse("PT15M"));
assertThat(DurationStyle.detectAndParse("+PT15M"))
.isEqualTo(Duration.parse("PT15M"));
assertThat(DurationStyle.detectAndParse("PT10H"))
.isEqualTo(Duration.parse("PT10H"));
assertThat(DurationStyle.detectAndParse("P2D")).isEqualTo(Duration.parse("P2D"));
assertThat(DurationStyle.detectAndParse("P2DT3H4M"))
.isEqualTo(Duration.parse("P2DT3H4M"));
assertThat(DurationStyle.detectAndParse("-PT6H3M"))
.isEqualTo(Duration.parse("-PT6H3M"));
assertThat(DurationStyle.detectAndParse("-PT-6H+3M"))
.isEqualTo(Duration.parse("-PT-6H+3M"));
}
@Test
public void detectAndParseWhenSimpleNanosShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10ns")).isEqualTo(Duration.ofNanos(10));
assertThat(DurationStyle.detectAndParse("10NS")).isEqualTo(Duration.ofNanos(10));
assertThat(DurationStyle.detectAndParse("+10ns")).isEqualTo(Duration.ofNanos(10));
assertThat(DurationStyle.detectAndParse("-10ns"))
.isEqualTo(Duration.ofNanos(-10));
}
@Test
public void detectAndParseWhenSimpleMillisShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10ms")).isEqualTo(Duration.ofMillis(10));
assertThat(DurationStyle.detectAndParse("10MS")).isEqualTo(Duration.ofMillis(10));
assertThat(DurationStyle.detectAndParse("+10ms"))
.isEqualTo(Duration.ofMillis(10));
assertThat(DurationStyle.detectAndParse("-10ms"))
.isEqualTo(Duration.ofMillis(-10));
}
@Test
public void detectAndParseWhenSimpleSecondsShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10s")).isEqualTo(Duration.ofSeconds(10));
assertThat(DurationStyle.detectAndParse("10S")).isEqualTo(Duration.ofSeconds(10));
assertThat(DurationStyle.detectAndParse("+10s"))
.isEqualTo(Duration.ofSeconds(10));
assertThat(DurationStyle.detectAndParse("-10s"))
.isEqualTo(Duration.ofSeconds(-10));
}
@Test
public void detectAndParseWhenSimpleMinutesShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10m")).isEqualTo(Duration.ofMinutes(10));
assertThat(DurationStyle.detectAndParse("10M")).isEqualTo(Duration.ofMinutes(10));
assertThat(DurationStyle.detectAndParse("+10m"))
.isEqualTo(Duration.ofMinutes(10));
assertThat(DurationStyle.detectAndParse("-10m"))
.isEqualTo(Duration.ofMinutes(-10));
}
@Test
public void detectAndParseWhenSimpleHoursShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10h")).isEqualTo(Duration.ofHours(10));
assertThat(DurationStyle.detectAndParse("10H")).isEqualTo(Duration.ofHours(10));
assertThat(DurationStyle.detectAndParse("+10h")).isEqualTo(Duration.ofHours(10));
assertThat(DurationStyle.detectAndParse("-10h")).isEqualTo(Duration.ofHours(-10));
}
@Test
public void detectAndParseWhenSimpleDaysShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10d")).isEqualTo(Duration.ofDays(10));
assertThat(DurationStyle.detectAndParse("10D")).isEqualTo(Duration.ofDays(10));
assertThat(DurationStyle.detectAndParse("+10d")).isEqualTo(Duration.ofDays(10));
assertThat(DurationStyle.detectAndParse("-10d")).isEqualTo(Duration.ofDays(-10));
}
@Test
public void detectAndParseWhenSimpleWithoutSuffixShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10")).isEqualTo(Duration.ofMillis(10));
assertThat(DurationStyle.detectAndParse("+10")).isEqualTo(Duration.ofMillis(10));
assertThat(DurationStyle.detectAndParse("-10")).isEqualTo(Duration.ofMillis(-10));
}
@Test
public void detectAndParseWhenSimpleWithoutSuffixButWithChronoUnitShouldReturnDuration() {
assertThat(DurationStyle.detectAndParse("10", ChronoUnit.SECONDS))
.isEqualTo(Duration.ofSeconds(10));
assertThat(DurationStyle.detectAndParse("+10", ChronoUnit.SECONDS))
.isEqualTo(Duration.ofSeconds(10));
assertThat(DurationStyle.detectAndParse("-10", ChronoUnit.SECONDS))
.isEqualTo(Duration.ofSeconds(-10));
}
@Test
public void detectAndParseWhenBadFormatShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("'10foo' is not a valid duration");
DurationStyle.detectAndParse("10foo");
}
@Test
public void detectWhenSimpleShouldReturnSimple() {
assertThat(DurationStyle.detect("10")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("+10")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("-10")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10ns")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10ms")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10s")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10m")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10h")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10d")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("-10ms")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("-10ms")).isEqualTo(DurationStyle.SIMPLE);
assertThat(DurationStyle.detect("10D")).isEqualTo(DurationStyle.SIMPLE);
}
@Test
public void detectWhenIso8601ShouldReturnIso8601() {
assertThat(DurationStyle.detect("PT20.345S")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("PT15M")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("+PT15M")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("PT10H")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("P2D")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("P2DT3H4M")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("-PT6H3M")).isEqualTo(DurationStyle.ISO8601);
assertThat(DurationStyle.detect("-PT-6H+3M")).isEqualTo(DurationStyle.ISO8601);
}
@Test
public void detectWhenUnknownShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("'bad' is not a valid duration");
DurationStyle.detect("bad");
}
@Test
public void parseIso8601ShouldParse() {
assertThat(DurationStyle.ISO8601.parse("PT20.345S"))
.isEqualTo(Duration.parse("PT20.345S"));
assertThat(DurationStyle.ISO8601.parse("PT15M"))
.isEqualTo(Duration.parse("PT15M"));
assertThat(DurationStyle.ISO8601.parse("+PT15M"))
.isEqualTo(Duration.parse("PT15M"));
assertThat(DurationStyle.ISO8601.parse("PT10H"))
.isEqualTo(Duration.parse("PT10H"));
assertThat(DurationStyle.ISO8601.parse("P2D")).isEqualTo(Duration.parse("P2D"));
assertThat(DurationStyle.ISO8601.parse("P2DT3H4M"))
.isEqualTo(Duration.parse("P2DT3H4M"));
assertThat(DurationStyle.ISO8601.parse("-PT6H3M"))
.isEqualTo(Duration.parse("-PT6H3M"));
assertThat(DurationStyle.ISO8601.parse("-PT-6H+3M"))
.isEqualTo(Duration.parse("-PT-6H+3M"));
}
@Test
public void parseIso8601WithUnitShouldIgnoreUnit() {
assertThat(DurationStyle.ISO8601.parse("PT20.345S", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("PT20.345S"));
assertThat(DurationStyle.ISO8601.parse("PT15M", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("PT15M"));
assertThat(DurationStyle.ISO8601.parse("+PT15M", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("PT15M"));
assertThat(DurationStyle.ISO8601.parse("PT10H", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("PT10H"));
assertThat(DurationStyle.ISO8601.parse("P2D")).isEqualTo(Duration.parse("P2D"));
assertThat(DurationStyle.ISO8601.parse("P2DT3H4M", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("P2DT3H4M"));
assertThat(DurationStyle.ISO8601.parse("-PT6H3M", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("-PT6H3M"));
assertThat(DurationStyle.ISO8601.parse("-PT-6H+3M", ChronoUnit.SECONDS))
.isEqualTo(Duration.parse("-PT-6H+3M"));
}
@Test
public void parseIso8601WhenSimpleShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("'10d' is not a valid ISO-8601 duration");
DurationStyle.ISO8601.parse("10d");
}
@Test
public void parseSimpleShouldParse() {
assertThat(DurationStyle.SIMPLE.parse("10m")).isEqualTo(Duration.ofMinutes(10));
}
@Test
public void parseSimpleWithUnitShouldUseUnitAsFallback() {
assertThat(DurationStyle.SIMPLE.parse("10m", ChronoUnit.SECONDS))
.isEqualTo(Duration.ofMinutes(10));
assertThat(DurationStyle.SIMPLE.parse("10", ChronoUnit.MINUTES))
.isEqualTo(Duration.ofMinutes(10));
}
@Test
public void parseSimpleWhenUnknownUnitShouldThrowException() {
try {
DurationStyle.SIMPLE.parse("10mb");
fail("Did not throw");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getCause().getMessage()).isEqualTo("Unknown unit 'mb'");
}
}
@Test
public void parseSimpleWhenIso8601ShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("'PT10H' is not a valid simple duration");
DurationStyle.SIMPLE.parse("PT10H");
}
@Test
public void printIso8601ShouldPrint() {
Duration duration = Duration.parse("-PT-6H+3M");
assertThat(DurationStyle.ISO8601.print(duration)).isEqualTo("PT5H57M");
}
@Test
public void printIso8601ShouldIgnoreUnit() {
Duration duration = Duration.parse("-PT-6H+3M");
assertThat(DurationStyle.ISO8601.print(duration, ChronoUnit.DAYS))
.isEqualTo("PT5H57M");
}
@Test
public void printSimpleWithoutUnitShouldPrintInMs() {
Duration duration = Duration.ofSeconds(1);
assertThat(DurationStyle.SIMPLE.print(duration)).isEqualTo("1000ms");
}
@Test
public void printSimpleWithUnitShouldPrintInUnit() {
Duration duration = Duration.ofMillis(1000);
assertThat(DurationStyle.SIMPLE.print(duration, ChronoUnit.SECONDS))
.isEqualTo("1s");
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DurationToNumberConverter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class DurationToNumberConverterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private final ConversionService conversionService;
public DurationToNumberConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertWithoutStyleShouldReturnMs() {
Long converted = this.conversionService.convert(Duration.ofSeconds(1),
Long.class);
assertThat(converted).isEqualTo(1000);
}
@Test
public void convertWithFormatShouldUseIgnoreFormat() {
Integer converted = (Integer) this.conversionService.convert(
Duration.ofSeconds(1),
MockDurationTypeDescriptor.get(null, DurationStyle.ISO8601),
TypeDescriptor.valueOf(Integer.class));
assertThat(converted).isEqualTo(1000);
}
@Test
public void convertWithFormatAndUnitShouldUseFormatAndUnit() {
Byte converted = (Byte) this.conversionService.convert(Duration.ofSeconds(1),
MockDurationTypeDescriptor.get(ChronoUnit.SECONDS, null),
TypeDescriptor.valueOf(Byte.class));
assertThat(converted).isEqualTo((byte) 1);
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new DurationToNumberConverter());
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DurationToStringConverter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class DurationToStringConverterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private final ConversionService conversionService;
public DurationToStringConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertWithoutStyleShouldReturnIso8601() {
String converted = this.conversionService.convert(Duration.ofSeconds(1),
String.class);
assertThat(converted).isEqualTo("PT1S");
}
@Test
public void convertWithFormatShouldUseFormatAndMs() {
String converted = (String) this.conversionService.convert(Duration.ofSeconds(1),
MockDurationTypeDescriptor.get(null, DurationStyle.SIMPLE),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("1000ms");
}
@Test
public void convertWithFormatAndUnitShouldUseFormatAndUnit() {
String converted = (String) this.conversionService.convert(Duration.ofSeconds(1),
MockDurationTypeDescriptor.get(ChronoUnit.SECONDS, DurationStyle.SIMPLE),
TypeDescriptor.valueOf(String.class));
assertThat(converted).isEqualTo("1s");
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new DurationToStringConverter());
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.AssumptionViolatedException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link InetAddressFormatter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class InetAddressFormatterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private final ConversionService conversionService;
public InetAddressFormatterTests(String name, ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertFromInetAddressToStringShouldConvert()
throws UnknownHostException {
assumeResolves("example.com");
InetAddress address = InetAddress.getByName("example.com");
String converted = this.conversionService.convert(address, String.class);
assertThat(converted).isEqualTo(address.getHostAddress());
}
@Test
public void convertFromStringToInetAddressShouldConvert() {
assumeResolves("example.com");
InetAddress converted = this.conversionService.convert("example.com",
InetAddress.class);
assertThat(converted.toString()).startsWith("example.com");
}
@Test
public void convertFromStringToInetAddressWhenHostDoesNotExistShouldThrowException() {
this.thrown.expect(ConversionFailedException.class);
this.conversionService.convert("ireallydontexist.example.com", InetAddress.class);
}
private void assumeResolves(String host) {
try {
InetAddress.getByName(host);
}
catch (UnknownHostException ex) {
throw new AssumptionViolatedException("Host " + host + " not resolvable", ex);
}
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new InetAddressFormatter());
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link IsoOffsetFormatter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class IsoOffsetFormatterTests {
private final ConversionService conversionService;
public IsoOffsetFormatterTests(String name, ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertShouldConvertStringToIsoDate() {
OffsetDateTime now = OffsetDateTime.now();
OffsetDateTime converted = this.conversionService.convert(
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(now), OffsetDateTime.class);
assertThat(converted).isEqualTo(now);
}
@Test
public void convertShouldConvertIsoDateToString() {
OffsetDateTime now = OffsetDateTime.now();
String converted = this.conversionService.convert(now, String.class);
assertThat(converted).isNotNull()
.startsWith(now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new IsoOffsetFormatter());
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.TypeDescriptor;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Create a mock {@link TypeDescriptor} with optional {@link DurationUnit} and
* {@link DurationFormat} annotations.
*
* @author Phillip Webb
*/
public final class MockDurationTypeDescriptor {
private MockDurationTypeDescriptor() {
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static TypeDescriptor get(ChronoUnit unit, DurationStyle style) {
TypeDescriptor descriptor = mock(TypeDescriptor.class);
if (unit != null) {
DurationUnit unitAnnotation = AnnotationUtils.synthesizeAnnotation(
Collections.singletonMap("value", unit), DurationUnit.class, null);
given(descriptor.getAnnotation(DurationUnit.class))
.willReturn(unitAnnotation);
}
if (style != null) {
DurationFormat formatAnnotation = AnnotationUtils.synthesizeAnnotation(
Collections.singletonMap("value", style), DurationFormat.class, null);
given(descriptor.getAnnotation(DurationFormat.class))
.willReturn(formatAnnotation);
}
given(descriptor.getType()).willReturn((Class) Duration.class);
given(descriptor.getObjectType()).willReturn((Class) Duration.class);
return descriptor;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2012-2018 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.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link NumberToDurationConverter}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class NumberToDurationConverterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private final ConversionService conversionService;
public NumberToDurationConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertWhenSimpleWithoutSuffixShouldReturnDuration() {
assertThat(convert(10)).isEqualTo(Duration.ofMillis(10));
assertThat(convert(+10)).isEqualTo(Duration.ofMillis(10));
assertThat(convert(-10)).isEqualTo(Duration.ofMillis(-10));
}
@Test
public void convertWhenSimpleWithoutSuffixButWithAnnotationShouldReturnDuration() {
assertThat(convert(10, ChronoUnit.SECONDS)).isEqualTo(Duration.ofSeconds(10));
assertThat(convert(+10, ChronoUnit.SECONDS)).isEqualTo(Duration.ofSeconds(10));
assertThat(convert(-10, ChronoUnit.SECONDS)).isEqualTo(Duration.ofSeconds(-10));
}
private Duration convert(Integer source) {
return this.conversionService.convert(source, Duration.class);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Duration convert(Integer source, ChronoUnit defaultUnit) {
TypeDescriptor targetType = mock(TypeDescriptor.class);
if (defaultUnit != null) {
DurationUnit unitAnnotation = AnnotationUtils.synthesizeAnnotation(
Collections.singletonMap("value", defaultUnit), DurationUnit.class,
null);
given(targetType.getAnnotation(DurationUnit.class))
.willReturn(unitAnnotation);
}
given(targetType.getType()).willReturn((Class) Duration.class);
return (Duration) this.conversionService.convert(source,
TypeDescriptor.forObject(source), targetType);
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new NumberToDurationConverter());
}
}

View File

@@ -14,34 +14,41 @@
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
package org.springframework.boot.convert;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DurationConverter}.
* Tests for {@link StringToDurationConverter}.
*
* @author Phillip Webb
*/
public class DurationConverterTests {
@RunWith(Parameterized.class)
public class StringToDurationConverterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private DurationConverter converter = new DurationConverter();
private final ConversionService conversionService;
public StringToDurationConverterTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void convertWhenIso8601ShouldReturnDuration() {
@@ -112,36 +119,45 @@ public class DurationConverterTests {
@Test
public void convertWhenSimpleWithoutSuffixButWithAnnotationShouldReturnDuration() {
assertThat(convert("10", ChronoUnit.SECONDS)).isEqualTo(Duration.ofSeconds(10));
assertThat(convert("+10", ChronoUnit.SECONDS)).isEqualTo(Duration.ofSeconds(10));
assertThat(convert("-10", ChronoUnit.SECONDS)).isEqualTo(Duration.ofSeconds(-10));
assertThat(convert("10", ChronoUnit.SECONDS, null))
.isEqualTo(Duration.ofSeconds(10));
assertThat(convert("+10", ChronoUnit.SECONDS, null))
.isEqualTo(Duration.ofSeconds(10));
assertThat(convert("-10", ChronoUnit.SECONDS, null))
.isEqualTo(Duration.ofSeconds(-10));
}
@Test
public void convertWhenBadFormatShouldThrowException() {
this.thrown.expect(IllegalStateException.class);
this.thrown.expect(ConversionFailedException.class);
this.thrown.expectMessage("'10foo' is not a valid duration");
convert("10foo");
}
@Test
public void convertWhenStyleMismatchShouldThrowException() {
this.thrown.expect(ConversionFailedException.class);
convert("10s", null, DurationStyle.ISO8601);
}
@Test
public void convertWhenEmptyShouldReturnNull() {
assertThat(convert("")).isNull();
}
private Duration convert(String source) {
return (Duration) this.converter.convert(source, TypeDescriptor.forObject(source),
TypeDescriptor.valueOf(Duration.class));
return this.conversionService.convert(source, Duration.class);
}
private Duration convert(String source, ChronoUnit defaultUnit) {
TypeDescriptor targetType = mock(TypeDescriptor.class);
DefaultDurationUnit annotation = AnnotationUtils.synthesizeAnnotation(
Collections.singletonMap("value", defaultUnit), DefaultDurationUnit.class,
null);
given(targetType.getAnnotation(DefaultDurationUnit.class)).willReturn(annotation);
return (Duration) this.converter.convert(source, TypeDescriptor.forObject(source),
targetType);
private Duration convert(String source, ChronoUnit unit, DurationStyle style) {
return (Duration) this.conversionService.convert(source,
TypeDescriptor.forObject(source),
MockDurationTypeDescriptor.get(unit, style));
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(new StringToDurationConverter());
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2012-2018 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.boot.convert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.convert.ConversionService;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link StringToEnumIgnoringCaseConverterFactory}.
*
* @author Phillip Webb
*/
@RunWith(Parameterized.class)
public class StringToEnumIgnoringCaseConverterFactoryTests {
private final ConversionService conversionService;
public StringToEnumIgnoringCaseConverterFactoryTests(String name,
ConversionService conversionService) {
this.conversionService = conversionService;
}
@Test
public void canConvertFromStringToEnumShouldReturnTrue() {
assertThat(this.conversionService.canConvert(String.class, TestEnum.class))
.isTrue();
}
@Test
public void canConvertFromStringToEnumSubclassShouldReturnTrue() {
assertThat(this.conversionService.canConvert(String.class,
TestSubclassEnum.ONE.getClass())).isTrue();
}
@Test
public void convertFromStringToEnumWhenExactMatchShouldConvertValue() {
ConversionService service = this.conversionService;
assertThat(service.convert("", TestEnum.class)).isNull();
assertThat(service.convert("ONE", TestEnum.class)).isEqualTo(TestEnum.ONE);
assertThat(service.convert("TWO", TestEnum.class)).isEqualTo(TestEnum.TWO);
assertThat(service.convert("THREE_AND_FOUR", TestEnum.class))
.isEqualTo(TestEnum.THREE_AND_FOUR);
}
@Test
public void convertFromStringToEnumWhenFuzzyMatchShouldConvertValue() {
ConversionService service = this.conversionService;
assertThat(service.convert("", TestEnum.class)).isNull();
assertThat(service.convert("one", TestEnum.class)).isEqualTo(TestEnum.ONE);
assertThat(service.convert("tWo", TestEnum.class)).isEqualTo(TestEnum.TWO);
assertThat(service.convert("three_and_four", TestEnum.class))
.isEqualTo(TestEnum.THREE_AND_FOUR);
assertThat(service.convert("threeandfour", TestEnum.class))
.isEqualTo(TestEnum.THREE_AND_FOUR);
assertThat(service.convert("three-and-four", TestEnum.class))
.isEqualTo(TestEnum.THREE_AND_FOUR);
assertThat(service.convert("threeAndFour", TestEnum.class))
.isEqualTo(TestEnum.THREE_AND_FOUR);
}
@Parameters(name = "{0}")
public static Iterable<Object[]> conversionServices() {
return new ConversionServiceParameters(
new StringToEnumIgnoringCaseConverterFactory());
}
enum TestEnum {
ONE, TWO, THREE_AND_FOUR
}
enum TestSubclassEnum {
ONE {
@Override
public String toString() {
return "foo";
}
}
}
}