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:
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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 + "'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user