diff --git a/src/main/java/ch/jalu/configme/properties/OptionalProperty.java b/src/main/java/ch/jalu/configme/properties/OptionalProperty.java index 55e6e32a..5b882c76 100644 --- a/src/main/java/ch/jalu/configme/properties/OptionalProperty.java +++ b/src/main/java/ch/jalu/configme/properties/OptionalProperty.java @@ -1,70 +1,50 @@ package ch.jalu.configme.properties; -import ch.jalu.configme.properties.convertresult.PropertyValue; -import ch.jalu.configme.resource.PropertyReader; +import ch.jalu.configme.properties.types.OptionalPropertyType; +import ch.jalu.configme.properties.types.PropertyType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Optional; /** - * Property which may be empty. - *

- * Wraps another property with an {@link Optional}: if a property is not present in the property resource, - * {@link Optional#empty} is returned. + * Optional property. Properties of this type may be absent from a property resource and it will still be considered + * valid. * * @param the type of value */ -public class OptionalProperty implements Property> { - - private final Property baseProperty; - private final Optional defaultValue; - - public OptionalProperty(@NotNull Property baseProperty) { - this.baseProperty = baseProperty; - this.defaultValue = Optional.empty(); - } - - public OptionalProperty(@NotNull Property baseProperty, @NotNull T defaultValue) { - this.baseProperty = baseProperty; - this.defaultValue = Optional.of(defaultValue); - } - - @Override - public @NotNull String getPath() { - return baseProperty.getPath(); - } - - @Override - public @NotNull PropertyValue> determineValue(@NotNull PropertyReader reader) { - PropertyValue basePropertyValue = baseProperty.determineValue(reader); - Optional value = basePropertyValue.isValidInResource() - ? Optional.ofNullable(basePropertyValue.getValue()) - : Optional.empty(); - - // Propagate the false "valid" property if the reader has a value at the base property's path - // and the base property says it's invalid -> triggers a rewrite to get rid of the invalid value. - boolean isWrongInResource = !basePropertyValue.isValidInResource() && reader.contains(baseProperty.getPath()); - return isWrongInResource - ? PropertyValue.withValueRequiringRewrite(value) - : PropertyValue.withValidValue(value); - } - - @Override - public @NotNull Optional getDefaultValue() { - return defaultValue; +public class OptionalProperty extends TypeBasedProperty> { + + /** + * Constructor. Creates a new property with an empty Optional as default value. + * + * @param path the path of the property + * @param valueType the property type of the value inside the optional + */ + public OptionalProperty(@NotNull String path, @NotNull PropertyType valueType) { + this(path, new OptionalPropertyType<>(valueType), Optional.empty()); } - @Override - public boolean isValidValue(@Nullable Optional value) { - if (value == null) { - return false; - } - return value.map(baseProperty::isValidValue).orElse(true); + /** + * Constructor. + * + * @param path the path of the property + * @param valueType the property type of the value inside the optional + * @param defaultValue the default value of the property (will be wrapped in an Optional) + */ + public OptionalProperty(@NotNull String path, @NotNull PropertyType valueType, @Nullable T defaultValue) { + this(path, new OptionalPropertyType<>(valueType), Optional.ofNullable(defaultValue)); } - @Override - public @Nullable Object toExportValue(@NotNull Optional value) { - return value.map(baseProperty::toExportValue).orElse(null); + /** + * Constructor. + * + * @param path the path of the property + * @param type the type of this property + * @param defaultValue the default value of this property + */ + public OptionalProperty(@NotNull String path, @NotNull PropertyType> type, + @NotNull Optional defaultValue) { + super(path, type, defaultValue); } } diff --git a/src/main/java/ch/jalu/configme/properties/PropertyInitializer.java b/src/main/java/ch/jalu/configme/properties/PropertyInitializer.java index 95afed5c..9b9e66f4 100644 --- a/src/main/java/ch/jalu/configme/properties/PropertyInitializer.java +++ b/src/main/java/ch/jalu/configme/properties/PropertyInitializer.java @@ -4,8 +4,15 @@ import ch.jalu.configme.properties.builder.CollectionPropertyBuilder; import ch.jalu.configme.properties.builder.MapPropertyBuilder; import ch.jalu.configme.properties.types.ArrayPropertyType; +import ch.jalu.configme.properties.types.BooleanType; +import ch.jalu.configme.properties.types.EnumPropertyType; import ch.jalu.configme.properties.types.InlineArrayPropertyType; +import ch.jalu.configme.properties.types.ListPropertyType; +import ch.jalu.configme.properties.types.NumberType; import ch.jalu.configme.properties.types.PropertyType; +import ch.jalu.configme.properties.types.RegexType; +import ch.jalu.configme.properties.types.SetPropertyType; +import ch.jalu.configme.properties.types.StringType; import org.jetbrains.annotations.NotNull; import java.time.LocalDate; @@ -311,48 +318,47 @@ public static ArrayPropertyBuilder> inlineArrayPro // Optional flavors // -------------- public static @NotNull OptionalProperty optionalBooleanProperty(@NotNull String path) { - return new OptionalProperty<>(new BooleanProperty(path, false)); + return new OptionalProperty<>(path, BooleanType.BOOLEAN); } public static @NotNull OptionalProperty optionalShortProperty(@NotNull String path) { - return new OptionalProperty<>(new ShortProperty(path, (short) 0)); + return new OptionalProperty<>(path, NumberType.SHORT); } public static @NotNull OptionalProperty optionalIntegerProperty(@NotNull String path) { - return new OptionalProperty<>(new IntegerProperty(path, 0)); + return new OptionalProperty<>(path, NumberType.INTEGER); } public static @NotNull OptionalProperty optionalLongProperty(@NotNull String path) { - return new OptionalProperty<>(new LongProperty(path, 0L)); + return new OptionalProperty<>(path, NumberType.LONG); } public static @NotNull OptionalProperty optionalFloatProperty(@NotNull String path) { - return new OptionalProperty<>(new FloatProperty(path, 0f)); + return new OptionalProperty<>(path, NumberType.FLOAT); } public static @NotNull OptionalProperty optionalDoubleProperty(@NotNull String path) { - return new OptionalProperty<>(new DoubleProperty(path, 0.0)); + return new OptionalProperty<>(path, NumberType.DOUBLE); } public static @NotNull OptionalProperty optionalStringProperty(@NotNull String path) { - return new OptionalProperty<>(new StringProperty(path, "")); + return new OptionalProperty<>(path, StringType.STRING); } public static > @NotNull OptionalProperty optionalEnumProperty(@NotNull Class clazz, @NotNull String path) { - // default value may never be null, so get the first entry in the enum class - return new OptionalProperty<>(new EnumProperty<>(path, clazz, clazz.getEnumConstants()[0])); + return new OptionalProperty<>(path, new EnumPropertyType<>(clazz)); } public static @NotNull OptionalProperty optionalRegexProperty(@NotNull String path) { - return new OptionalProperty<>(new RegexProperty(path, "")); + return new OptionalProperty<>(path, RegexType.REGEX); } public static @NotNull OptionalProperty> optionalListProperty(@NotNull String path) { - return new OptionalProperty<>(new StringListProperty(path)); + return new OptionalProperty<>(path, new ListPropertyType<>(StringType.STRING)); } public static @NotNull OptionalProperty> optionalSetProperty(@NotNull String path) { - return new OptionalProperty<>(new StringSetProperty(path)); + return new OptionalProperty<>(path, new SetPropertyType<>(StringType.STRING)); } } diff --git a/src/main/java/ch/jalu/configme/properties/types/OptionalPropertyType.java b/src/main/java/ch/jalu/configme/properties/types/OptionalPropertyType.java new file mode 100644 index 00000000..a8e78720 --- /dev/null +++ b/src/main/java/ch/jalu/configme/properties/types/OptionalPropertyType.java @@ -0,0 +1,39 @@ +package ch.jalu.configme.properties.types; + +import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +/** + * Property type for optionals. Wraps another property type. + * + * @param the value type of the optional + */ +public class OptionalPropertyType implements PropertyType> { + + private final PropertyType valueType; + + /** + * Constructor. + * + * @param valueType the property type to handle the value inside the optional + */ + public OptionalPropertyType(PropertyType valueType) { + this.valueType = valueType; + } + + @Override + public @NotNull Optional convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) { + if (object != null) { + return Optional.ofNullable(valueType.convert(object, errorRecorder)); + } + return Optional.empty(); + } + + @Override + public @Nullable Object toExportValue(@NotNull Optional value) { + return value.map(valueType::toExportValue).orElse(null); + } +} diff --git a/src/test/java/ch/jalu/configme/SettingsManagerImplTest.java b/src/test/java/ch/jalu/configme/SettingsManagerImplTest.java index 64b5f900..53967f06 100644 --- a/src/test/java/ch/jalu/configme/SettingsManagerImplTest.java +++ b/src/test/java/ch/jalu/configme/SettingsManagerImplTest.java @@ -9,6 +9,7 @@ import ch.jalu.configme.properties.BeanProperty; import ch.jalu.configme.properties.OptionalProperty; import ch.jalu.configme.properties.Property; +import ch.jalu.configme.properties.types.NumberType; import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.resource.PropertyResource; import ch.jalu.configme.resource.YamlFileResource; @@ -203,7 +204,7 @@ void shouldSetOptionalPropertyCorrectly() { PropertyResource resource = new YamlFileResource(file); SettingsManager settingsManager = new SettingsManagerImpl(resource, createConfiguration(TestConfiguration.class), null); - OptionalProperty intOptional = new OptionalProperty<>(newProperty("version", 65)); + OptionalProperty intOptional = new OptionalProperty<>("version", NumberType.INTEGER); // when settingsManager.setProperty(intOptional, Optional.empty()); diff --git a/src/test/java/ch/jalu/configme/properties/OptionalPropertyTest.java b/src/test/java/ch/jalu/configme/properties/OptionalPropertyTest.java index f7e19055..175bf3ba 100644 --- a/src/test/java/ch/jalu/configme/properties/OptionalPropertyTest.java +++ b/src/test/java/ch/jalu/configme/properties/OptionalPropertyTest.java @@ -1,6 +1,11 @@ package ch.jalu.configme.properties; import ch.jalu.configme.properties.convertresult.PropertyValue; +import ch.jalu.configme.properties.types.BooleanType; +import ch.jalu.configme.properties.types.EnumPropertyType; +import ch.jalu.configme.properties.types.NumberType; +import ch.jalu.configme.properties.types.PropertyType; +import ch.jalu.configme.properties.types.StringType; import ch.jalu.configme.resource.PropertyReader; import ch.jalu.configme.samples.TestEnum; import org.junit.jupiter.api.Test; @@ -9,17 +14,16 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; +import java.util.concurrent.TimeUnit; import static ch.jalu.configme.TestUtils.isErrorValueOf; import static ch.jalu.configme.TestUtils.isValidValueOf; import static java.util.Optional.of; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.only; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.mock; /** * Test for {@link OptionalProperty}. @@ -33,9 +37,9 @@ class OptionalPropertyTest { @Test void shouldReturnPresentValues() { // given - OptionalProperty booleanProp = new OptionalProperty<>(new BooleanProperty("bool.path.test", false)); - OptionalProperty intProp = new OptionalProperty<>(new IntegerProperty("int.path.test", 0)); - OptionalProperty enumProp = new OptionalProperty<>(new EnumProperty<>("enum.path.test", TestEnum.class, TestEnum.SECOND)); + OptionalProperty booleanProp = new OptionalProperty<>("bool.path.test", BooleanType.BOOLEAN); + OptionalProperty intProp = new OptionalProperty<>("int.path.test", NumberType.INTEGER); + OptionalProperty enumProp = new OptionalProperty<>("enum.path.test", new EnumPropertyType<>(TestEnum.class)); given(reader.getObject("bool.path.test")).willReturn(true); given(reader.getObject("int.path.test")).willReturn(27); @@ -55,9 +59,9 @@ void shouldReturnPresentValues() { @Test void shouldReturnEmptyOptional() { // given - OptionalProperty booleanProp = new OptionalProperty<>(new BooleanProperty("bool.path.wrong", false)); - OptionalProperty intProp = new OptionalProperty<>(new IntegerProperty("int.path.wrong", 0)); - OptionalProperty enumProp = new OptionalProperty<>(new EnumProperty<>("enum.path.wrong", TestEnum.class, TestEnum.SECOND)); + OptionalProperty booleanProp = new OptionalProperty<>("bool.path.wrong", BooleanType.BOOLEAN); + OptionalProperty intProp = new OptionalProperty<>("int.path.wrong", NumberType.INTEGER); + OptionalProperty enumProp = new OptionalProperty<>("enum.path.wrong", new EnumPropertyType<>(TestEnum.class)); // when PropertyValue> boolResult = booleanProp.determineValue(reader); @@ -73,7 +77,7 @@ void shouldReturnEmptyOptional() { @Test void shouldAllowToDefineDefaultValue() { // given - OptionalProperty integerProp = new OptionalProperty<>(new IntegerProperty("path", 0), 42); + OptionalProperty integerProp = new OptionalProperty<>("int.path.wrong", NumberType.INTEGER, 42); // when Optional defaultValue = integerProp.getDefaultValue(); @@ -85,48 +89,67 @@ void shouldAllowToDefineDefaultValue() { @Test void shouldReturnValueWithInvalidFlagIfReturnedFromReader() { // given - StringProperty baseProperty = spy(new StringProperty("the.path", "DEFAULT")); - doReturn(PropertyValue.withValueRequiringRewrite("this should be discarded")).when(baseProperty).determineValue(reader); - given(reader.contains("the.path")).willReturn(true); - OptionalProperty optionalProperty = new OptionalProperty<>(baseProperty); + given(reader.getObject("the.path")).willReturn(400); + OptionalProperty optionalProperty = new OptionalProperty<>("the.path", NumberType.BYTE); // when - PropertyValue> value = optionalProperty.determineValue(reader); + PropertyValue> value = optionalProperty.determineValue(reader); // then - assertThat(value, isErrorValueOf(Optional.empty())); + assertThat(value, isErrorValueOf(Optional.of(Byte.MAX_VALUE))); } @Test - void shouldDelegateToBasePropertyAndHaveEmptyOptionalAsDefault() { + void shouldValidateWithBasePropertyNullSafe() { // given - StringProperty baseProperty = new StringProperty("some.path", "Def"); - OptionalProperty property = new OptionalProperty<>(baseProperty); + OptionalProperty property = new OptionalProperty<>("path", StringType.STRING); // when - Optional defaultValue = property.getDefaultValue(); - String path = property.getPath(); + boolean isEmptyValid = property.isValidValue(Optional.empty()); + boolean isValueValid = property.isValidValue(Optional.of("foo")); + boolean isNullValid = property.isValidValue(null); // then - assertThat(defaultValue, equalTo(Optional.empty())); - assertThat(path, equalTo("some.path")); + assertThat(isEmptyValid, equalTo(true)); + assertThat(isValueValid, equalTo(true)); + assertThat(isNullValid, equalTo(false)); } @Test - void shouldValidateWithBasePropertyNullSafe() { + void shouldReturnNullAsExportValue() { // given - StringProperty baseProperty = spy(new StringProperty("some.path", "Def")); - OptionalProperty property = new OptionalProperty<>(baseProperty); + OptionalProperty property = new OptionalProperty<>("int.path", NumberType.INTEGER); // when - boolean isEmptyValid = property.isValidValue(Optional.empty()); - boolean isValueValid = property.isValidValue(Optional.of("foo")); - boolean isNullValid = property.isValidValue(null); + Object exportValue = property.toExportValue(Optional.empty()); // then - assertThat(isEmptyValid, equalTo(true)); - assertThat(isValueValid, equalTo(true)); - assertThat(isNullValid, equalTo(false)); - verify(baseProperty, only()).isValidValue("foo"); + assertThat(exportValue, nullValue()); + } + + @Test + void shouldReturnNullIfValuePropertyTypeReturnsNull() { + // given + PropertyType valuePropertyType = mock(PropertyType.class); + given(valuePropertyType.toExportValue("demo")).willReturn(null); + OptionalProperty optionalProperty = new OptionalProperty<>("int.path", valuePropertyType); + + // when + Object exportValue = optionalProperty.toExportValue(Optional.of("demo")); + + // then + assertThat(exportValue, nullValue()); + } + + @Test + void shouldConstructExportValue() { + // given + OptionalProperty optionalProperty = new OptionalProperty<>("duration.unit", EnumPropertyType.of(TimeUnit.class)); + + // when + Object exportValue = optionalProperty.toExportValue(Optional.of(TimeUnit.HOURS)); + + // then + assertThat(exportValue, equalTo("HOURS")); } } diff --git a/src/test/java/ch/jalu/configme/resource/UniqueCommentTest.java b/src/test/java/ch/jalu/configme/resource/UniqueCommentTest.java index 743673ed..fb55a6ca 100644 --- a/src/test/java/ch/jalu/configme/resource/UniqueCommentTest.java +++ b/src/test/java/ch/jalu/configme/resource/UniqueCommentTest.java @@ -5,7 +5,6 @@ import ch.jalu.configme.TestUtils; import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.configurationdata.ConfigurationDataBuilder; -import ch.jalu.configme.properties.BeanProperty; import ch.jalu.configme.properties.ListProperty; import ch.jalu.configme.properties.MapProperty; import ch.jalu.configme.properties.OptionalProperty; @@ -141,7 +140,7 @@ public static final class ServerSettingHolder implements SettingsHolder { new ServerCollection(true, "reception"), new ServerCollection(false, "lobby")); public static final Property> ALT = - new OptionalProperty<>(new BeanProperty<>("alternative", ServerCollection.class, new ServerCollection())); + new OptionalProperty<>("alternative", BeanPropertyType.of(ServerCollection.class)); private ServerSettingHolder() { } diff --git a/src/test/java/ch/jalu/configme/resource/YamlFileResourceTest.java b/src/test/java/ch/jalu/configme/resource/YamlFileResourceTest.java index c37c7fb5..d825c16c 100644 --- a/src/test/java/ch/jalu/configme/resource/YamlFileResourceTest.java +++ b/src/test/java/ch/jalu/configme/resource/YamlFileResourceTest.java @@ -8,6 +8,8 @@ import ch.jalu.configme.properties.BeanProperty; import ch.jalu.configme.properties.OptionalProperty; import ch.jalu.configme.properties.Property; +import ch.jalu.configme.properties.types.EnumPropertyType; +import ch.jalu.configme.properties.types.NumberType; import ch.jalu.configme.samples.TestConfiguration; import ch.jalu.configme.samples.TestEnum; import org.junit.jupiter.api.Test; @@ -195,8 +197,8 @@ void shouldExportConfigurationWithExpectedComments() throws IOException { void shouldSkipAbsentOptionalProperty() throws IOException { // given ConfigurationData configurationData = createConfiguration(asList( - new OptionalProperty<>(TestConfiguration.DURATION_IN_SECONDS), - new OptionalProperty<>(TestConfiguration.RATIO_ORDER))); + new OptionalProperty<>("test.duration", NumberType.INTEGER), + new OptionalProperty<>("sample.ratio.order", new EnumPropertyType<>(TestEnum.class)))); Path file = copyFileFromResources(INCOMPLETE_FILE); PropertyResource resource = new YamlFileResource(file); configurationData.initializeValues(resource.createReader()); @@ -216,8 +218,8 @@ void shouldSkipAbsentOptionalProperty() throws IOException { void shouldExportAllPresentOptionalProperties() throws IOException { // given ConfigurationData configurationData = createConfiguration(asList( - new OptionalProperty<>(TestConfiguration.DURATION_IN_SECONDS), - new OptionalProperty<>(TestConfiguration.RATIO_ORDER))); + new OptionalProperty<>("test.duration", NumberType.INTEGER), + new OptionalProperty<>("sample.ratio.order", new EnumPropertyType<>(TestEnum.class)))); Path file = copyFileFromResources(COMPLETE_FILE); PropertyResource resource = new YamlFileResource(file); configurationData.initializeValues(resource.createReader());