diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 1e225ca07d..8d39881548 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1308,6 +1308,45 @@ while supporting a much richer format. +[[boot-features-external-config-conversion-datasize]] +===== Converting Data Sizes +Spring Framework has a `DataSize` value type that allows to express size in bytes. If you +expose a `DataSize` property, the following formats in application properties are +available: + +* A regular `long` representation (using bytes as the default unit unless a +`@DataSizeUnit` has been specified) +* A more readable format where the value and the unit are coupled (e.g. `10MB` means 10 +megabytes) + +Consider the following example: + +[source,java,indent=0] +---- +include::{code-examples}/context/properties/bind/AppIoProperties.java[tag=example] +---- + +To specify a buffer size of 10 megabytes, `10` and `10MB` are equivalent. A size threshold +of 256 bytes can be specified as `256` or `256B`. + +You can also use any of the supported unit. These are: + +* `B` for bytes +* `KB` for kilobytes +* `MB` for megabytes +* `GB` for gigabytes +* `TB` for terabytes + +The default unit is bytes and can be overridden using `@DataSizeUnit` as illustrated +in the sample above. + +TIP: If you are upgrading from a previous version that is simply using `Long` to express +the size, make sure to define the unit (using `@DataSizeUnit`) if it isn't bytes alongside +the switch to `DataSize`. Doing so gives a transparent upgrade path while supporting a +much richer format. + + + [[boot-features-external-config-validation]] ==== @ConfigurationProperties Validation Spring Boot attempts to validate `@ConfigurationProperties` classes whenever they are diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/AppIoProperties.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/AppIoProperties.java new file mode 100644 index 0000000000..3b7f3fec22 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/properties/bind/AppIoProperties.java @@ -0,0 +1,55 @@ +/* + * 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.docs.context.properties.bind; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.convert.DataSizeUnit; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +/** + * A {@link ConfigurationProperties} example that uses {@link DataSize}. + * + * @author Stephane Nicoll + */ +// tag::example[] +@ConfigurationProperties("app.io") +public class AppIoProperties { + + @DataSizeUnit(DataUnit.MEGABYTES) + private DataSize bufferSize = DataSize.ofMegaBytes(2); + + private DataSize sizeThreshold = DataSize.ofBytes(512); + + public DataSize getBufferSize() { + return this.bufferSize; + } + + public void setBufferSize(DataSize bufferSize) { + this.bufferSize = bufferSize; + } + + public DataSize getSizeThreshold() { + return this.sizeThreshold; + } + + public void setSizeThreshold(DataSize sizeThreshold) { + this.sizeThreshold = sizeThreshold; + } + +} +// end::example[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java index a56ce83b5c..bb0a061ba9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java @@ -116,6 +116,20 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser { DURATION_SUFFIX = Collections.unmodifiableMap(values); } + private static final String DATA_SIZE_OF = "DataSize.of"; + + private static final Map DATA_SIZE_SUFFIX; + + static { + Map values = new HashMap<>(); + values.put("Bytes", "B"); + values.put("KiloBytes", "KB"); + values.put("MegaBytes", "MB"); + values.put("GigaBytes", "GB"); + values.put("TeraBytes", "TB"); + DATA_SIZE_SUFFIX = Collections.unmodifiableMap(values); + } + private final Map fieldValues = new HashMap<>(); private final Map staticFinals = new HashMap<>(); @@ -173,14 +187,29 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser { } private Object getFactoryValue(ExpressionTree expression, Object factoryValue) { + Object durationValue = getFactoryValue(expression, factoryValue, DURATION_OF, + DURATION_SUFFIX); + if (durationValue != null) { + return durationValue; + } + Object dataSizeValue = getFactoryValue(expression, factoryValue, DATA_SIZE_OF, + DATA_SIZE_SUFFIX); + if (dataSizeValue != null) { + return dataSizeValue; + } + return factoryValue; + } + + private Object getFactoryValue(ExpressionTree expression, Object factoryValue, + String prefix, Map suffixMapping) { Object instance = expression.getInstance(); - if (instance != null && instance.toString().startsWith(DURATION_OF)) { + if (instance != null && instance.toString().startsWith(prefix)) { String type = instance.toString(); - type = type.substring(DURATION_OF.length(), type.indexOf('(')); - String suffix = DURATION_SUFFIX.get(type); + type = type.substring(prefix.length(), type.indexOf('(')); + String suffix = suffixMapping.get(type); return (suffix != null) ? factoryValue + suffix : null; } - return factoryValue; + return null; } public Map getFieldValues() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java index 02e66206c4..3031a93f5c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java @@ -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. @@ -95,6 +95,12 @@ public abstract class AbstractFieldValuesProcessorTests { assertThat(values.get("durationMinutes")).isEqualTo("30m"); assertThat(values.get("durationHours")).isEqualTo("40h"); assertThat(values.get("durationDays")).isEqualTo("50d"); + assertThat(values.get("dataSizeNone")).isNull(); + assertThat(values.get("dataSizeBytes")).isEqualTo("5B"); + assertThat(values.get("dataSizeKiloBytes")).isEqualTo("10KB"); + assertThat(values.get("dataSizeMegaBytes")).isEqualTo("20MB"); + assertThat(values.get("dataSizeGigaBytes")).isEqualTo("30GB"); + assertThat(values.get("dataSizeTeraBytes")).isEqualTo("40TB"); } @SupportedAnnotationTypes({ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java index 94d46e1d80..493bc7185f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java @@ -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. @@ -22,6 +22,7 @@ import java.time.Duration; import org.springframework.boot.configurationsample.ConfigurationProperties; import org.springframework.util.MimeType; +import org.springframework.util.unit.DataSize; /** * Sample object containing fields with initial values. @@ -123,4 +124,16 @@ public class FieldValues { private Duration durationDays = Duration.ofDays(50); + private DataSize dataSizeNone; + + private DataSize dataSizeBytes = DataSize.ofBytes(5); + + private DataSize dataSizeKiloBytes = DataSize.ofKiloBytes(10); + + private DataSize dataSizeMegaBytes = DataSize.ofMegaBytes(20); + + private DataSize dataSizeGigaBytes = DataSize.ofGigaBytes(30); + + private DataSize dataSizeTeraBytes = DataSize.ofTeraBytes(40); + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java index a5470427d9..89b18c956a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java @@ -99,6 +99,7 @@ public class ApplicationConversionService extends FormattingConversionService { registry.addConverter(new DurationToStringConverter()); registry.addConverter(new NumberToDurationConverter()); registry.addConverter(new DurationToNumberConverter()); + registry.addConverter(new StringToDataSizeConverter()); registry.addConverterFactory(new StringToEnumIgnoringCaseConverterFactory()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java new file mode 100644 index 0000000000..e4cb225fe3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java @@ -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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +/** + * Annotation that can be used to change the default unit used when converting a + * {@link DataSize}. + * + * @author Stephane Nicoll + * @since 2.1.0 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataSizeUnit { + + /** + * The {@link DataUnit} to use if one is not specified. + * @return the data unit + */ + DataUnit value(); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java new file mode 100644 index 0000000000..3198fd2b5d --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java @@ -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.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; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +/** + * {@link Converter} to convert from a {@link String} to a {@link DataSize}. Supports + * {@link DataSize#parse(CharSequence)}. + * + * @author Stephane Nicoll + * @see DataSizeUnit + */ +final class StringToDataSizeConverter implements GenericConverter { + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, DataSize.class)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, + TypeDescriptor targetType) { + if (ObjectUtils.isEmpty(source)) { + return null; + } + return convert(source.toString(), getDataUnit(targetType)); + } + + private DataUnit getDataUnit(TypeDescriptor targetType) { + DataSizeUnit annotation = targetType.getAnnotation(DataSizeUnit.class); + return (annotation != null) ? annotation.value() : null; + } + + private DataSize convert(String source, DataUnit unit) { + return DataSize.parse(source, unit); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index dab87db0f9..7ea030a1a3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -49,6 +49,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.validation.BindValidationException; +import org.springframework.boot.convert.DataSizeUnit; import org.springframework.boot.testsupport.rule.OutputCapture; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -72,6 +73,8 @@ import org.springframework.mock.env.MockEnvironment; import org.springframework.stereotype.Component; import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.util.StringUtils; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; @@ -767,6 +770,14 @@ public class ConfigurationPropertiesTests { assertThat(bean.getFile()).isEqualTo(new File(".")); } + @Test + public void loadWhenBindingToDataSizeShouldBind() { + load(DataSizeProperties.class, "test.size=10GB", "test.another-size=5"); + DataSizeProperties bean = this.context.getBean(DataSizeProperties.class); + assertThat(bean.getSize()).isEqualTo(DataSize.ofGigaBytes(10)); + assertThat(bean.getAnotherSize()).isEqualTo(DataSize.ofKiloBytes(5)); + } + @Test public void loadWhenTopLevelConverterNotFoundExceptionShouldNotFail() { load(PersonProperties.class, "test=boot"); @@ -1692,6 +1703,33 @@ public class ConfigurationPropertiesTests { } + @EnableConfigurationProperties + @ConfigurationProperties(prefix = "test") + static class DataSizeProperties { + + private DataSize size; + + @DataSizeUnit(DataUnit.KILOBYTES) + private DataSize anotherSize; + + public DataSize getSize() { + return this.size; + } + + public void setSize(DataSize size) { + this.size = size; + } + + public DataSize getAnotherSize() { + return this.anotherSize; + } + + public void setAnotherSize(DataSize anotherSize) { + this.anotherSize = anotherSize; + } + + } + static class CustomPropertiesValidator implements Validator { @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/MockDataSizeTypeDescriptor.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/MockDataSizeTypeDescriptor.java new file mode 100644 index 0000000000..97c64a5637 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/MockDataSizeTypeDescriptor.java @@ -0,0 +1,53 @@ +/* + * 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.Collections; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Create a mock {@link TypeDescriptor} with optional {@link DataUnit} annotation. + * + * @author Stephane Nicoll + */ +public final class MockDataSizeTypeDescriptor { + + private MockDataSizeTypeDescriptor() { + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static TypeDescriptor get(DataUnit unit) { + TypeDescriptor descriptor = mock(TypeDescriptor.class); + if (unit != null) { + DataSizeUnit unitAnnotation = AnnotationUtils.synthesizeAnnotation( + Collections.singletonMap("value", unit), DataSizeUnit.class, null); + given(descriptor.getAnnotation(DataSizeUnit.class)) + .willReturn(unitAnnotation); + } + given(descriptor.getType()).willReturn((Class) DataSize.class); + given(descriptor.getObjectType()).willReturn((Class) DataSize.class); + return descriptor; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/StringToDataSizeConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/StringToDataSizeConverterTests.java new file mode 100644 index 0000000000..a97bc4604a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/StringToDataSizeConverterTests.java @@ -0,0 +1,129 @@ +/* + * 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.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 org.springframework.core.convert.TypeDescriptor; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link StringToDataSizeConverter}. + * + * @author Stephane Nicoll + */ +@RunWith(Parameterized.class) +public class StringToDataSizeConverterTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final ConversionService conversionService; + + public StringToDataSizeConverterTests(String name, + ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Test + public void convertWhenSimpleBytesShouldReturnDataSize() { + assertThat(convert("10B")).isEqualTo(DataSize.ofBytes(10)); + assertThat(convert("+10B")).isEqualTo(DataSize.ofBytes(10)); + assertThat(convert("-10B")).isEqualTo(DataSize.ofBytes(-10)); + } + + @Test + public void convertWhenSimpleKiloBytesShouldReturnDataSize() { + assertThat(convert("10KB")).isEqualTo(DataSize.ofKiloBytes(10)); + assertThat(convert("+10KB")).isEqualTo(DataSize.ofKiloBytes(10)); + assertThat(convert("-10KB")).isEqualTo(DataSize.ofKiloBytes(-10)); + } + + @Test + public void convertWhenSimpleMegaBytesShouldReturnDataSize() { + assertThat(convert("10MB")).isEqualTo(DataSize.ofMegaBytes(10)); + assertThat(convert("+10MB")).isEqualTo(DataSize.ofMegaBytes(10)); + assertThat(convert("-10MB")).isEqualTo(DataSize.ofMegaBytes(-10)); + } + + @Test + public void convertWhenSimpleGigaBytesShouldReturnDataSize() { + assertThat(convert("10GB")).isEqualTo(DataSize.ofGigaBytes(10)); + assertThat(convert("+10GB")).isEqualTo(DataSize.ofGigaBytes(10)); + assertThat(convert("-10GB")).isEqualTo(DataSize.ofGigaBytes(-10)); + } + + @Test + public void convertWhenSimpleTeraBytesShouldReturnDataSize() { + assertThat(convert("10TB")).isEqualTo(DataSize.ofTeraBytes(10)); + assertThat(convert("+10TB")).isEqualTo(DataSize.ofTeraBytes(10)); + assertThat(convert("-10TB")).isEqualTo(DataSize.ofTeraBytes(-10)); + } + + @Test + public void convertWhenSimpleWithoutSuffixShouldReturnDataSize() { + assertThat(convert("10")).isEqualTo(DataSize.ofBytes(10)); + assertThat(convert("+10")).isEqualTo(DataSize.ofBytes(10)); + assertThat(convert("-10")).isEqualTo(DataSize.ofBytes(-10)); + } + + @Test + public void convertWhenSimpleWithoutSuffixButWithAnnotationShouldReturnDataSize() { + assertThat(convert("10", DataUnit.KILOBYTES)).isEqualTo(DataSize.ofKiloBytes(10)); + assertThat(convert("+10", DataUnit.KILOBYTES)) + .isEqualTo(DataSize.ofKiloBytes(10)); + assertThat(convert("-10", DataUnit.KILOBYTES)) + .isEqualTo(DataSize.ofKiloBytes(-10)); + } + + @Test + public void convertWhenBadFormatShouldThrowException() { + this.thrown.expect(ConversionFailedException.class); + this.thrown.expectMessage("'10WB' is not a valid data size"); + convert("10WB"); + } + + @Test + public void convertWhenEmptyShouldReturnNull() { + assertThat(convert("")).isNull(); + } + + private DataSize convert(String source) { + return this.conversionService.convert(source, DataSize.class); + } + + private DataSize convert(String source, DataUnit unit) { + return (DataSize) this.conversionService.convert(source, + TypeDescriptor.forObject(source), MockDataSizeTypeDescriptor.get(unit)); + } + + @Parameters(name = "{0}") + public static Iterable conversionServices() { + return new ConversionServiceParameters(new StringToDataSizeConverter()); + } + +}