Skip to content

Commit d0e9c8f

Browse files
authored
Merge pull request #521 from AuthMe/448-optional-property-from-property-type
#448 Refactor OptionalProperty to be based on a property type
2 parents ab96873 + 84f8967 commit d0e9c8f

File tree

7 files changed

+156
-106
lines changed

7 files changed

+156
-106
lines changed
Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,50 @@
11
package ch.jalu.configme.properties;
22

3-
import ch.jalu.configme.properties.convertresult.PropertyValue;
4-
import ch.jalu.configme.resource.PropertyReader;
3+
import ch.jalu.configme.properties.types.OptionalPropertyType;
4+
import ch.jalu.configme.properties.types.PropertyType;
55
import org.jetbrains.annotations.NotNull;
66
import org.jetbrains.annotations.Nullable;
77

88
import java.util.Optional;
99

1010
/**
11-
* Property which may be empty.
12-
* <p>
13-
* Wraps another property with an {@link Optional}: if a property is not present in the property resource,
14-
* {@link Optional#empty} is returned.
11+
* Optional property. Properties of this type may be absent from a property resource and it will still be considered
12+
* valid.
1513
*
1614
* @param <T> the type of value
1715
*/
18-
public class OptionalProperty<T> implements Property<Optional<T>> {
19-
20-
private final Property<T> baseProperty;
21-
private final Optional<T> defaultValue;
22-
23-
public OptionalProperty(@NotNull Property<T> baseProperty) {
24-
this.baseProperty = baseProperty;
25-
this.defaultValue = Optional.empty();
26-
}
27-
28-
public OptionalProperty(@NotNull Property<T> baseProperty, @NotNull T defaultValue) {
29-
this.baseProperty = baseProperty;
30-
this.defaultValue = Optional.of(defaultValue);
31-
}
32-
33-
@Override
34-
public @NotNull String getPath() {
35-
return baseProperty.getPath();
36-
}
37-
38-
@Override
39-
public @NotNull PropertyValue<Optional<T>> determineValue(@NotNull PropertyReader reader) {
40-
PropertyValue<T> basePropertyValue = baseProperty.determineValue(reader);
41-
Optional<T> value = basePropertyValue.isValidInResource()
42-
? Optional.ofNullable(basePropertyValue.getValue())
43-
: Optional.empty();
44-
45-
// Propagate the false "valid" property if the reader has a value at the base property's path
46-
// and the base property says it's invalid -> triggers a rewrite to get rid of the invalid value.
47-
boolean isWrongInResource = !basePropertyValue.isValidInResource() && reader.contains(baseProperty.getPath());
48-
return isWrongInResource
49-
? PropertyValue.withValueRequiringRewrite(value)
50-
: PropertyValue.withValidValue(value);
51-
}
52-
53-
@Override
54-
public @NotNull Optional<T> getDefaultValue() {
55-
return defaultValue;
16+
public class OptionalProperty<T> extends TypeBasedProperty<Optional<T>> {
17+
18+
/**
19+
* Constructor. Creates a new property with an empty Optional as default value.
20+
*
21+
* @param path the path of the property
22+
* @param valueType the property type of the value inside the optional
23+
*/
24+
public OptionalProperty(@NotNull String path, @NotNull PropertyType<T> valueType) {
25+
this(path, new OptionalPropertyType<>(valueType), Optional.empty());
5626
}
5727

58-
@Override
59-
public boolean isValidValue(@Nullable Optional<T> value) {
60-
if (value == null) {
61-
return false;
62-
}
63-
return value.map(baseProperty::isValidValue).orElse(true);
28+
/**
29+
* Constructor.
30+
*
31+
* @param path the path of the property
32+
* @param valueType the property type of the value inside the optional
33+
* @param defaultValue the default value of the property (will be wrapped in an Optional)
34+
*/
35+
public OptionalProperty(@NotNull String path, @NotNull PropertyType<T> valueType, @Nullable T defaultValue) {
36+
this(path, new OptionalPropertyType<>(valueType), Optional.ofNullable(defaultValue));
6437
}
6538

66-
@Override
67-
public @Nullable Object toExportValue(@NotNull Optional<T> value) {
68-
return value.map(baseProperty::toExportValue).orElse(null);
39+
/**
40+
* Constructor.
41+
*
42+
* @param path the path of the property
43+
* @param type the type of this property
44+
* @param defaultValue the default value of this property
45+
*/
46+
public OptionalProperty(@NotNull String path, @NotNull PropertyType<Optional<T>> type,
47+
@NotNull Optional<T> defaultValue) {
48+
super(path, type, defaultValue);
6949
}
7050
}

src/main/java/ch/jalu/configme/properties/PropertyInitializer.java

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
import ch.jalu.configme.properties.builder.CollectionPropertyBuilder;
55
import ch.jalu.configme.properties.builder.MapPropertyBuilder;
66
import ch.jalu.configme.properties.types.ArrayPropertyType;
7+
import ch.jalu.configme.properties.types.BooleanType;
8+
import ch.jalu.configme.properties.types.EnumPropertyType;
79
import ch.jalu.configme.properties.types.InlineArrayPropertyType;
10+
import ch.jalu.configme.properties.types.ListPropertyType;
11+
import ch.jalu.configme.properties.types.NumberType;
812
import ch.jalu.configme.properties.types.PropertyType;
13+
import ch.jalu.configme.properties.types.RegexType;
14+
import ch.jalu.configme.properties.types.SetPropertyType;
15+
import ch.jalu.configme.properties.types.StringType;
916
import org.jetbrains.annotations.NotNull;
1017

1118
import java.time.LocalDate;
@@ -311,48 +318,47 @@ public static <E> ArrayPropertyBuilder<E, InlineArrayProperty<E>> inlineArrayPro
311318
// Optional flavors
312319
// --------------
313320
public static @NotNull OptionalProperty<Boolean> optionalBooleanProperty(@NotNull String path) {
314-
return new OptionalProperty<>(new BooleanProperty(path, false));
321+
return new OptionalProperty<>(path, BooleanType.BOOLEAN);
315322
}
316323

317324
public static @NotNull OptionalProperty<Short> optionalShortProperty(@NotNull String path) {
318-
return new OptionalProperty<>(new ShortProperty(path, (short) 0));
325+
return new OptionalProperty<>(path, NumberType.SHORT);
319326
}
320327

321328
public static @NotNull OptionalProperty<Integer> optionalIntegerProperty(@NotNull String path) {
322-
return new OptionalProperty<>(new IntegerProperty(path, 0));
329+
return new OptionalProperty<>(path, NumberType.INTEGER);
323330
}
324331

325332
public static @NotNull OptionalProperty<Long> optionalLongProperty(@NotNull String path) {
326-
return new OptionalProperty<>(new LongProperty(path, 0L));
333+
return new OptionalProperty<>(path, NumberType.LONG);
327334
}
328335

329336
public static @NotNull OptionalProperty<Float> optionalFloatProperty(@NotNull String path) {
330-
return new OptionalProperty<>(new FloatProperty(path, 0f));
337+
return new OptionalProperty<>(path, NumberType.FLOAT);
331338
}
332339

333340
public static @NotNull OptionalProperty<Double> optionalDoubleProperty(@NotNull String path) {
334-
return new OptionalProperty<>(new DoubleProperty(path, 0.0));
341+
return new OptionalProperty<>(path, NumberType.DOUBLE);
335342
}
336343

337344
public static @NotNull OptionalProperty<String> optionalStringProperty(@NotNull String path) {
338-
return new OptionalProperty<>(new StringProperty(path, ""));
345+
return new OptionalProperty<>(path, StringType.STRING);
339346
}
340347

341348
public static <E extends Enum<E>> @NotNull OptionalProperty<E> optionalEnumProperty(@NotNull Class<E> clazz,
342349
@NotNull String path) {
343-
// default value may never be null, so get the first entry in the enum class
344-
return new OptionalProperty<>(new EnumProperty<>(path, clazz, clazz.getEnumConstants()[0]));
350+
return new OptionalProperty<>(path, new EnumPropertyType<>(clazz));
345351
}
346352

347353
public static @NotNull OptionalProperty<Pattern> optionalRegexProperty(@NotNull String path) {
348-
return new OptionalProperty<>(new RegexProperty(path, ""));
354+
return new OptionalProperty<>(path, RegexType.REGEX);
349355
}
350356

351357
public static @NotNull OptionalProperty<List<String>> optionalListProperty(@NotNull String path) {
352-
return new OptionalProperty<>(new StringListProperty(path));
358+
return new OptionalProperty<>(path, new ListPropertyType<>(StringType.STRING));
353359
}
354360

355361
public static @NotNull OptionalProperty<Set<String>> optionalSetProperty(@NotNull String path) {
356-
return new OptionalProperty<>(new StringSetProperty(path));
362+
return new OptionalProperty<>(path, new SetPropertyType<>(StringType.STRING));
357363
}
358364
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package ch.jalu.configme.properties.types;
2+
3+
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import java.util.Optional;
8+
9+
/**
10+
* Property type for optionals. Wraps another property type.
11+
*
12+
* @param <T> the value type of the optional
13+
*/
14+
public class OptionalPropertyType<T> implements PropertyType<Optional<T>> {
15+
16+
private final PropertyType<T> valueType;
17+
18+
/**
19+
* Constructor.
20+
*
21+
* @param valueType the property type to handle the value inside the optional
22+
*/
23+
public OptionalPropertyType(PropertyType<T> valueType) {
24+
this.valueType = valueType;
25+
}
26+
27+
@Override
28+
public @NotNull Optional<T> convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
29+
if (object != null) {
30+
return Optional.ofNullable(valueType.convert(object, errorRecorder));
31+
}
32+
return Optional.empty();
33+
}
34+
35+
@Override
36+
public @Nullable Object toExportValue(@NotNull Optional<T> value) {
37+
return value.map(valueType::toExportValue).orElse(null);
38+
}
39+
}

src/test/java/ch/jalu/configme/SettingsManagerImplTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import ch.jalu.configme.properties.BeanProperty;
1010
import ch.jalu.configme.properties.OptionalProperty;
1111
import ch.jalu.configme.properties.Property;
12+
import ch.jalu.configme.properties.types.NumberType;
1213
import ch.jalu.configme.resource.PropertyReader;
1314
import ch.jalu.configme.resource.PropertyResource;
1415
import ch.jalu.configme.resource.YamlFileResource;
@@ -203,7 +204,7 @@ void shouldSetOptionalPropertyCorrectly() {
203204
PropertyResource resource = new YamlFileResource(file);
204205
SettingsManager settingsManager =
205206
new SettingsManagerImpl(resource, createConfiguration(TestConfiguration.class), null);
206-
OptionalProperty<Integer> intOptional = new OptionalProperty<>(newProperty("version", 65));
207+
OptionalProperty<Integer> intOptional = new OptionalProperty<>("version", NumberType.INTEGER);
207208

208209
// when
209210
settingsManager.setProperty(intOptional, Optional.empty());
Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package ch.jalu.configme.properties;
22

33
import ch.jalu.configme.properties.convertresult.PropertyValue;
4+
import ch.jalu.configme.properties.types.BooleanType;
5+
import ch.jalu.configme.properties.types.EnumPropertyType;
6+
import ch.jalu.configme.properties.types.NumberType;
7+
import ch.jalu.configme.properties.types.PropertyType;
8+
import ch.jalu.configme.properties.types.StringType;
49
import ch.jalu.configme.resource.PropertyReader;
510
import ch.jalu.configme.samples.TestEnum;
611
import org.junit.jupiter.api.Test;
@@ -9,17 +14,16 @@
914
import org.mockito.junit.jupiter.MockitoExtension;
1015

1116
import java.util.Optional;
17+
import java.util.concurrent.TimeUnit;
1218

1319
import static ch.jalu.configme.TestUtils.isErrorValueOf;
1420
import static ch.jalu.configme.TestUtils.isValidValueOf;
1521
import static java.util.Optional.of;
1622
import static org.hamcrest.MatcherAssert.assertThat;
1723
import static org.hamcrest.Matchers.equalTo;
24+
import static org.hamcrest.Matchers.nullValue;
1825
import static org.mockito.BDDMockito.given;
19-
import static org.mockito.Mockito.doReturn;
20-
import static org.mockito.Mockito.only;
21-
import static org.mockito.Mockito.spy;
22-
import static org.mockito.Mockito.verify;
26+
import static org.mockito.Mockito.mock;
2327

2428
/**
2529
* Test for {@link OptionalProperty}.
@@ -33,9 +37,9 @@ class OptionalPropertyTest {
3337
@Test
3438
void shouldReturnPresentValues() {
3539
// given
36-
OptionalProperty<Boolean> booleanProp = new OptionalProperty<>(new BooleanProperty("bool.path.test", false));
37-
OptionalProperty<Integer> intProp = new OptionalProperty<>(new IntegerProperty("int.path.test", 0));
38-
OptionalProperty<TestEnum> enumProp = new OptionalProperty<>(new EnumProperty<>("enum.path.test", TestEnum.class, TestEnum.SECOND));
40+
OptionalProperty<Boolean> booleanProp = new OptionalProperty<>("bool.path.test", BooleanType.BOOLEAN);
41+
OptionalProperty<Integer> intProp = new OptionalProperty<>("int.path.test", NumberType.INTEGER);
42+
OptionalProperty<TestEnum> enumProp = new OptionalProperty<>("enum.path.test", new EnumPropertyType<>(TestEnum.class));
3943

4044
given(reader.getObject("bool.path.test")).willReturn(true);
4145
given(reader.getObject("int.path.test")).willReturn(27);
@@ -55,9 +59,9 @@ void shouldReturnPresentValues() {
5559
@Test
5660
void shouldReturnEmptyOptional() {
5761
// given
58-
OptionalProperty<Boolean> booleanProp = new OptionalProperty<>(new BooleanProperty("bool.path.wrong", false));
59-
OptionalProperty<Integer> intProp = new OptionalProperty<>(new IntegerProperty("int.path.wrong", 0));
60-
OptionalProperty<TestEnum> enumProp = new OptionalProperty<>(new EnumProperty<>("enum.path.wrong", TestEnum.class, TestEnum.SECOND));
62+
OptionalProperty<Boolean> booleanProp = new OptionalProperty<>("bool.path.wrong", BooleanType.BOOLEAN);
63+
OptionalProperty<Integer> intProp = new OptionalProperty<>("int.path.wrong", NumberType.INTEGER);
64+
OptionalProperty<TestEnum> enumProp = new OptionalProperty<>("enum.path.wrong", new EnumPropertyType<>(TestEnum.class));
6165

6266
// when
6367
PropertyValue<Optional<Boolean>> boolResult = booleanProp.determineValue(reader);
@@ -73,7 +77,7 @@ void shouldReturnEmptyOptional() {
7377
@Test
7478
void shouldAllowToDefineDefaultValue() {
7579
// given
76-
OptionalProperty<Integer> integerProp = new OptionalProperty<>(new IntegerProperty("path", 0), 42);
80+
OptionalProperty<Integer> integerProp = new OptionalProperty<>("int.path.wrong", NumberType.INTEGER, 42);
7781

7882
// when
7983
Optional<Integer> defaultValue = integerProp.getDefaultValue();
@@ -85,48 +89,67 @@ void shouldAllowToDefineDefaultValue() {
8589
@Test
8690
void shouldReturnValueWithInvalidFlagIfReturnedFromReader() {
8791
// given
88-
StringProperty baseProperty = spy(new StringProperty("the.path", "DEFAULT"));
89-
doReturn(PropertyValue.withValueRequiringRewrite("this should be discarded")).when(baseProperty).determineValue(reader);
90-
given(reader.contains("the.path")).willReturn(true);
91-
OptionalProperty<String> optionalProperty = new OptionalProperty<>(baseProperty);
92+
given(reader.getObject("the.path")).willReturn(400);
93+
OptionalProperty<Byte> optionalProperty = new OptionalProperty<>("the.path", NumberType.BYTE);
9294

9395
// when
94-
PropertyValue<Optional<String>> value = optionalProperty.determineValue(reader);
96+
PropertyValue<Optional<Byte>> value = optionalProperty.determineValue(reader);
9597

9698
// then
97-
assertThat(value, isErrorValueOf(Optional.empty()));
99+
assertThat(value, isErrorValueOf(Optional.of(Byte.MAX_VALUE)));
98100
}
99101

100102
@Test
101-
void shouldDelegateToBasePropertyAndHaveEmptyOptionalAsDefault() {
103+
void shouldValidateWithBasePropertyNullSafe() {
102104
// given
103-
StringProperty baseProperty = new StringProperty("some.path", "Def");
104-
OptionalProperty<String> property = new OptionalProperty<>(baseProperty);
105+
OptionalProperty<String> property = new OptionalProperty<>("path", StringType.STRING);
105106

106107
// when
107-
Optional<String> defaultValue = property.getDefaultValue();
108-
String path = property.getPath();
108+
boolean isEmptyValid = property.isValidValue(Optional.empty());
109+
boolean isValueValid = property.isValidValue(Optional.of("foo"));
110+
boolean isNullValid = property.isValidValue(null);
109111

110112
// then
111-
assertThat(defaultValue, equalTo(Optional.empty()));
112-
assertThat(path, equalTo("some.path"));
113+
assertThat(isEmptyValid, equalTo(true));
114+
assertThat(isValueValid, equalTo(true));
115+
assertThat(isNullValid, equalTo(false));
113116
}
114117

115118
@Test
116-
void shouldValidateWithBasePropertyNullSafe() {
119+
void shouldReturnNullAsExportValue() {
117120
// given
118-
StringProperty baseProperty = spy(new StringProperty("some.path", "Def"));
119-
OptionalProperty<String> property = new OptionalProperty<>(baseProperty);
121+
OptionalProperty<Integer> property = new OptionalProperty<>("int.path", NumberType.INTEGER);
120122

121123
// when
122-
boolean isEmptyValid = property.isValidValue(Optional.empty());
123-
boolean isValueValid = property.isValidValue(Optional.of("foo"));
124-
boolean isNullValid = property.isValidValue(null);
124+
Object exportValue = property.toExportValue(Optional.empty());
125125

126126
// then
127-
assertThat(isEmptyValid, equalTo(true));
128-
assertThat(isValueValid, equalTo(true));
129-
assertThat(isNullValid, equalTo(false));
130-
verify(baseProperty, only()).isValidValue("foo");
127+
assertThat(exportValue, nullValue());
128+
}
129+
130+
@Test
131+
void shouldReturnNullIfValuePropertyTypeReturnsNull() {
132+
// given
133+
PropertyType<String> valuePropertyType = mock(PropertyType.class);
134+
given(valuePropertyType.toExportValue("demo")).willReturn(null);
135+
OptionalProperty<String> optionalProperty = new OptionalProperty<>("int.path", valuePropertyType);
136+
137+
// when
138+
Object exportValue = optionalProperty.toExportValue(Optional.of("demo"));
139+
140+
// then
141+
assertThat(exportValue, nullValue());
142+
}
143+
144+
@Test
145+
void shouldConstructExportValue() {
146+
// given
147+
OptionalProperty<TimeUnit> optionalProperty = new OptionalProperty<>("duration.unit", EnumPropertyType.of(TimeUnit.class));
148+
149+
// when
150+
Object exportValue = optionalProperty.toExportValue(Optional.of(TimeUnit.HOURS));
151+
152+
// then
153+
assertThat(exportValue, equalTo("HOURS"));
131154
}
132155
}

0 commit comments

Comments
 (0)