Skip to content

Commit 5a66ea4

Browse files
authored
Merge pull request #518 from thepoetdj/feature/date-time-type
Introducing TemporalType
2 parents cb71372 + 887d714 commit 5a66ea4

File tree

10 files changed

+500
-7
lines changed

10 files changed

+500
-7
lines changed

src/main/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import ch.jalu.configme.properties.types.NumberType;
88
import ch.jalu.configme.properties.types.RegexType;
99
import ch.jalu.configme.properties.types.StringType;
10+
import ch.jalu.configme.properties.types.TemporalType;
1011
import ch.jalu.typeresolver.TypeInfo;
1112
import org.jetbrains.annotations.NotNull;
1213
import org.jetbrains.annotations.Nullable;
@@ -74,7 +75,10 @@ public LeafValueHandlerImpl(@NotNull MapperLeafType @NotNull ... leafTypes) {
7475
NumberType.SHORT,
7576
NumberType.BIG_INTEGER,
7677
NumberType.BIG_DECIMAL,
77-
RegexType.REGEX)
78+
RegexType.REGEX,
79+
TemporalType.LOCAL_DATE,
80+
TemporalType.LOCAL_TIME,
81+
TemporalType.LOCAL_DATE_TIME)
7882
.collect(Collectors.toCollection(ArrayList::new));
7983
}
8084

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ch.jalu.configme.properties;
2+
3+
import ch.jalu.configme.properties.types.TemporalType;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
import java.time.LocalDate;
7+
8+
/**
9+
* {@link LocalDate} property.
10+
*/
11+
public class LocalDateProperty extends TypeBasedProperty<LocalDate> {
12+
13+
/**
14+
* Constructor.
15+
*
16+
* @param path the path of the property
17+
* @param defaultValue the default value of the property
18+
*/
19+
public LocalDateProperty(@NotNull String path, @NotNull LocalDate defaultValue) {
20+
super(path, TemporalType.LOCAL_DATE, defaultValue);
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ch.jalu.configme.properties;
2+
3+
import ch.jalu.configme.properties.types.TemporalType;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
import java.time.LocalDateTime;
7+
8+
/**
9+
* {@link LocalDateTime} property.
10+
*/
11+
public class LocalDateTimeProperty extends TypeBasedProperty<LocalDateTime> {
12+
13+
/**
14+
* Constructor.
15+
*
16+
* @param path the path of the property
17+
* @param defaultValue the default value of the property
18+
*/
19+
public LocalDateTimeProperty(@NotNull String path, @NotNull LocalDateTime defaultValue) {
20+
super(path, TemporalType.LOCAL_DATE_TIME, defaultValue);
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ch.jalu.configme.properties;
2+
3+
import ch.jalu.configme.properties.types.TemporalType;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
import java.time.LocalTime;
7+
8+
/**
9+
* {@link LocalTime} property.
10+
*/
11+
public class LocalTimeProperty extends TypeBasedProperty<LocalTime> {
12+
13+
/**
14+
* Constructor.
15+
*
16+
* @param path the path of the property
17+
* @param defaultValue the default value of the property
18+
*/
19+
public LocalTimeProperty(@NotNull String path, @NotNull LocalTime defaultValue) {
20+
super(path, TemporalType.LOCAL_TIME, defaultValue);
21+
}
22+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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.time.LocalDate;
8+
import java.time.LocalDateTime;
9+
import java.time.LocalTime;
10+
import java.time.format.DateTimeFormatter;
11+
import java.time.format.DateTimeParseException;
12+
import java.time.temporal.Temporal;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.function.BiFunction;
16+
17+
/**
18+
* Property type and mapper leaf type for temporal values.
19+
*
20+
* @param <T> the expected temporal type
21+
*/
22+
public class TemporalType<T extends Temporal> extends PropertyAndLeafType<T> {
23+
24+
/** Local Date temporal type. */
25+
public static final TemporalType<LocalDate> LOCAL_DATE = new TemporalType<>(
26+
LocalDate.class, Arrays.asList("yyyy-MM-dd", "dd.MM.yyyy", "MM/dd/yyyy"), LocalDate::parse);
27+
/** Local Time temporal type. */
28+
public static final TemporalType<LocalTime> LOCAL_TIME = new TemporalType<>(
29+
LocalTime.class, Arrays.asList("HH:mm:ss", "HH.mm", "HH:mm"), LocalTime::parse);
30+
/** Local Date Time temporal type. */
31+
public static final TemporalType<LocalDateTime> LOCAL_DATE_TIME = new TemporalType<>(
32+
LocalDateTime.class, Arrays.asList("yyyy-MM-dd HH:mm:ss", "dd.MM.yyyy HH:mm:ss", "MM/dd/yyyy HH:mm:ss"),
33+
LocalDateTime::parse);
34+
35+
private final List<String> supportedFormats;
36+
private final BiFunction<String, DateTimeFormatter, T> temporalParser;
37+
private String defaultExportFormat;
38+
39+
/**
40+
* Constructor.
41+
*
42+
* @param clazz the temporal type this type should convert to
43+
* @param supportedFormats list of conversion formats supported for this type
44+
* @param defaultParser function which can parse a value to this type in one of the given supportedFormats
45+
*/
46+
public TemporalType(@NotNull Class<T> clazz, @NotNull List<String> supportedFormats,
47+
@NotNull BiFunction<String, DateTimeFormatter, T> defaultParser) {
48+
super(clazz);
49+
if (supportedFormats.isEmpty()) {
50+
throw new IllegalArgumentException("At least one supported format must be provided.");
51+
}
52+
this.supportedFormats = supportedFormats;
53+
this.temporalParser = defaultParser;
54+
this.defaultExportFormat = supportedFormats.get(0);
55+
}
56+
57+
@Override
58+
public @Nullable T convert(@Nullable Object object, @NotNull ConvertErrorRecorder errorRecorder) {
59+
if (!(object instanceof String)) {
60+
return null;
61+
}
62+
return convertToTemporalType((String) object);
63+
}
64+
65+
@Override
66+
public @Nullable Object toExportValue(@NotNull T value) {
67+
return DateTimeFormatter.ofPattern(this.defaultExportFormat).format(value);
68+
}
69+
70+
private T convertToTemporalType(String temporalText) {
71+
for (String format: this.supportedFormats) {
72+
try {
73+
T parsedValue = this.temporalParser.apply(temporalText, DateTimeFormatter.ofPattern(format));
74+
this.defaultExportFormat = format;
75+
return parsedValue;
76+
} catch (DateTimeParseException e) {
77+
// try next format
78+
}
79+
}
80+
return null;
81+
}
82+
}

src/test/java/ch/jalu/configme/beanmapper/leafvaluehandler/LeafValueHandlerImplTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
import ch.jalu.configme.properties.types.NumberType;
1010
import ch.jalu.configme.properties.types.RegexType;
1111
import ch.jalu.configme.properties.types.StringType;
12+
import ch.jalu.configme.properties.types.TemporalType;
1213
import ch.jalu.typeresolver.typeimpl.WildcardTypeImpl;
1314
import org.junit.jupiter.api.Test;
1415
import org.junit.jupiter.api.extension.ExtendWith;
1516
import org.mockito.junit.jupiter.MockitoExtension;
1617

17-
import java.time.LocalDate;
1818
import java.util.ArrayList;
1919
import java.util.Arrays;
2020
import java.util.List;
@@ -59,7 +59,7 @@ void shouldReturnDefaultLeafTypes() {
5959
List<MapperLeafType> leafTypes = LeafValueHandlerImpl.createDefaultLeafTypes();
6060

6161
// then
62-
assertThat(leafTypes, hasSize(12));
62+
assertThat(leafTypes, hasSize(15));
6363
assertThat(leafTypes.get(0), sameInstance(BooleanType.BOOLEAN));
6464
assertThat(leafTypes.get(1), sameInstance(StringType.STRING));
6565
assertThat(leafTypes.get(2), sameInstance(NumberType.INTEGER));
@@ -72,6 +72,9 @@ void shouldReturnDefaultLeafTypes() {
7272
assertThat(leafTypes.get(9), sameInstance(NumberType.BIG_INTEGER));
7373
assertThat(leafTypes.get(10), sameInstance(NumberType.BIG_DECIMAL));
7474
assertThat(leafTypes.get(11), sameInstance(RegexType.REGEX));
75+
assertThat(leafTypes.get(12), sameInstance(TemporalType.LOCAL_DATE));
76+
assertThat(leafTypes.get(13), sameInstance(TemporalType.LOCAL_TIME));
77+
assertThat(leafTypes.get(14), sameInstance(TemporalType.LOCAL_DATE_TIME));
7578
}
7679

7780
@Test
@@ -96,7 +99,7 @@ void shouldCreateValueHandlerWithBuilder() {
9699
.addType(leafType1)
97100
.addDefaults()
98101
.addType(leafType2)
99-
.removeMatchingTypes(type -> type instanceof NumberType)
102+
.removeMatchingTypes(type -> type instanceof NumberType || type instanceof TemporalType)
100103
.build();
101104

102105
// then
@@ -166,13 +169,11 @@ void shouldNotConvertForUnsupportedTargetTypes() {
166169
Object object = "2020-02-13";
167170

168171
ConvertErrorRecorder errorRecorder = mock(ConvertErrorRecorder.class);
169-
MappingContext dateContext = MappingContextImpl.createRoot(of(LocalDate.class), errorRecorder);
170172
MappingContext wildcardContext = MappingContextImpl.createRoot(of(WildcardTypeImpl.newUnboundedWildcard()), errorRecorder);
171173

172174
LeafValueHandlerImpl leafValueHandler = new LeafValueHandlerImpl(LeafValueHandlerImpl.createDefaultLeafTypes());
173175

174176
// when / then
175-
assertThat(leafValueHandler.convert(object, dateContext), nullValue());
176177
assertThat(leafValueHandler.convert(object, wildcardContext), nullValue());
177178
}
178179

@@ -194,7 +195,6 @@ void shouldNotConvertUnsupportedValuesToExportValues() {
194195
ExportContext exportContext = ExportContextImpl.createRoot();
195196

196197
// when / then
197-
assertThat(leafValueHandler.toExportValue(LocalDate.now(), exportContext), nullValue());
198198
assertThat(leafValueHandler.toExportValue(new Object(), exportContext), nullValue());
199199
}
200200

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package ch.jalu.configme.properties;
2+
3+
import ch.jalu.configme.properties.convertresult.PropertyValue;
4+
import ch.jalu.configme.resource.PropertyReader;
5+
import org.junit.jupiter.api.BeforeAll;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.mockito.junit.jupiter.MockitoExtension;
9+
10+
import java.time.LocalDate;
11+
12+
import static ch.jalu.configme.TestUtils.isErrorValueOf;
13+
import static ch.jalu.configme.TestUtils.isValidValueOf;
14+
import static org.hamcrest.MatcherAssert.assertThat;
15+
import static org.hamcrest.Matchers.equalTo;
16+
import static org.mockito.Mockito.mock;
17+
import static org.mockito.Mockito.when;
18+
19+
/**
20+
* Test for {@link LocalDateProperty}.
21+
*/
22+
@ExtendWith(MockitoExtension.class)
23+
class LocalDatePropertyTest {
24+
25+
private static PropertyReader reader;
26+
27+
@BeforeAll
28+
static void setUpConfiguration() {
29+
reader = mock(PropertyReader.class);
30+
when(reader.getObject("local-date.path.test")).thenReturn("1999-01-31");
31+
when(reader.getObject("local-date.path.wrong")).thenReturn("31-01-1999");
32+
}
33+
34+
@Test
35+
void shouldGetLocalDateValue() {
36+
// given
37+
Property<LocalDate> property = new LocalDateProperty("local-date.path.test", LocalDate.of(1970, 1, 31));
38+
39+
// when
40+
PropertyValue<LocalDate> result = property.determineValue(reader);
41+
42+
//then
43+
assertThat(result, isValidValueOf(LocalDate.of(1999, 1, 31)));
44+
}
45+
46+
@Test
47+
void shouldGetLocalDateDefault() {
48+
// given
49+
LocalDate defaultValue = LocalDate.of(1970, 1, 31);
50+
Property<LocalDate> property = new LocalDateProperty("local-date.path.wrong", defaultValue);
51+
52+
// when
53+
PropertyValue<LocalDate> result = property.determineValue(reader);
54+
55+
// then
56+
assertThat(result, isErrorValueOf(defaultValue));
57+
}
58+
59+
@Test
60+
void shouldReturnValueForExport() {
61+
// given
62+
Property<LocalDate> property = new LocalDateProperty("export.path.local-date", LocalDate.of(1970, 1, 31));
63+
64+
// when
65+
Object exportValue = property.toExportValue(LocalDate.of(2000, 12, 31));
66+
67+
// then
68+
assertThat(exportValue, equalTo("2000-12-31"));
69+
}
70+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package ch.jalu.configme.properties;
2+
3+
import ch.jalu.configme.properties.convertresult.PropertyValue;
4+
import ch.jalu.configme.resource.PropertyReader;
5+
import org.junit.jupiter.api.BeforeAll;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.mockito.junit.jupiter.MockitoExtension;
9+
10+
import java.time.LocalDateTime;
11+
12+
import static ch.jalu.configme.TestUtils.isErrorValueOf;
13+
import static ch.jalu.configme.TestUtils.isValidValueOf;
14+
import static org.hamcrest.MatcherAssert.assertThat;
15+
import static org.hamcrest.Matchers.equalTo;
16+
import static org.mockito.Mockito.mock;
17+
import static org.mockito.Mockito.when;
18+
19+
/**
20+
* Test for {@link LocalDateTimeProperty}.
21+
*/
22+
@ExtendWith(MockitoExtension.class)
23+
class LocalDateTimePropertyTest {
24+
25+
private static PropertyReader reader;
26+
27+
@BeforeAll
28+
static void setUpConfiguration() {
29+
reader = mock(PropertyReader.class);
30+
when(reader.getObject("local-date-time.path.test")).thenReturn("2001-10-20 19:55:30");
31+
when(reader.getObject("local-date-time.path.wrong")).thenReturn("20-2001-10 55:19:30");
32+
}
33+
34+
@Test
35+
void shouldGetLocalDateTimeValue() {
36+
// given
37+
Property<LocalDateTime> property = new LocalDateTimeProperty("local-date-time.path.test", LocalDateTime.of(1970, 1, 31, 12, 0));
38+
39+
// when
40+
PropertyValue<LocalDateTime> result = property.determineValue(reader);
41+
42+
// then
43+
assertThat(result, isValidValueOf(LocalDateTime.of(2001, 10, 20, 19, 55, 30)));
44+
}
45+
46+
@Test
47+
void shouldGetLocalDateTimeDefault() {
48+
// given
49+
LocalDateTime defaultDateTime = LocalDateTime.of(1970, 1, 31, 12, 0);
50+
Property<LocalDateTime> property = new LocalDateTimeProperty("local-date-time.path.wrong", defaultDateTime);
51+
52+
// when
53+
PropertyValue<LocalDateTime> result = property.determineValue(reader);
54+
55+
// then
56+
assertThat(result, isErrorValueOf(defaultDateTime));
57+
}
58+
59+
@Test
60+
void shouldReturnValueForExport() {
61+
// given
62+
Property<LocalDateTime> property = new LocalDateTimeProperty("export.path.local-date-time", LocalDateTime.of(1970, 1, 31, 12, 0));
63+
64+
// when
65+
Object exportedValue = property.toExportValue(LocalDateTime.of(2001, 10, 22, 11, 39, 42));
66+
67+
// then
68+
assertThat(exportedValue, equalTo("2001-10-22 11:39:42"));
69+
}
70+
}

0 commit comments

Comments
 (0)