Skip to content

Commit 8226b25

Browse files
authored
Add temporal converter to support Java 8 time/date classes (#46)
This fixes #23
1 parent 4ca7798 commit 8226b25

File tree

4 files changed

+205
-4
lines changed

4 files changed

+205
-4
lines changed

org.eclipse.sisu.plexus/src/main/java/org/codehaus/plexus/component/configurator/converters/basic/AbstractBasicConverter.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public Object fromConfiguration( final ConverterLookup lookup, final PlexusConfi
4040
{
4141
try
4242
{
43-
result = fromString( (String) result );
43+
result = fromString( (String) result, type );
4444
}
4545
catch ( final ComponentConfigurationException e )
4646
{
@@ -58,15 +58,24 @@ public Object fromConfiguration( final ConverterLookup lookup, final PlexusConfi
5858
// Customizable methods
5959
// ----------------------------------------------------------------------
6060

61-
protected abstract Object fromString( final String str )
62-
throws ComponentConfigurationException;
61+
protected Object fromString( final String str, final Class<?> type )
62+
throws ComponentConfigurationException
63+
{
64+
return fromString( str );
65+
}
66+
67+
protected Object fromString( final String str )
68+
throws ComponentConfigurationException
69+
{
70+
throw new UnsupportedOperationException("The class " + this.getClass().getName() + " must implement one of the fromString(...) methods, but it doesn't");
71+
}
6372

6473
// ----------------------------------------------------------------------
6574
// Shared methods
6675
// ----------------------------------------------------------------------
6776

6877
@Override
69-
protected final Object fromExpression( final PlexusConfiguration configuration, final ExpressionEvaluator evaluator,
78+
protected Object fromExpression( final PlexusConfiguration configuration, final ExpressionEvaluator evaluator,
7079
final Class<?> type )
7180
throws ComponentConfigurationException
7281
{
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*******************************************************************************
2+
* All rights reserved. This program and the accompanying materials
3+
* are made available under the terms of the Eclipse Public License v1.0
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v10.html
6+
*******************************************************************************/
7+
package org.codehaus.plexus.component.configurator.converters.basic;
8+
9+
import java.time.Instant;
10+
import java.time.LocalDate;
11+
import java.time.LocalDateTime;
12+
import java.time.LocalTime;
13+
import java.time.OffsetDateTime;
14+
import java.time.OffsetTime;
15+
import java.time.ZoneId;
16+
import java.time.ZonedDateTime;
17+
import java.time.format.DateTimeFormatter;
18+
import java.time.format.DateTimeParseException;
19+
import java.time.temporal.Temporal;
20+
import java.time.temporal.TemporalAccessor;
21+
import java.util.Locale;
22+
23+
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
24+
25+
/**
26+
* Supports type conversion into {@link java.time} classes.
27+
* The supported patterns of the used {@link DateTimeFormatter} is either
28+
* <ul>
29+
* <li>ISO-8601 extended offset date-time-format ({@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}) or</li>
30+
* <li>{@code yyyy-MM-dd HH:mm:ss[[a][.S [a]]}</li>
31+
* </ul>
32+
*
33+
*/
34+
public class TemporalConverter
35+
extends AbstractBasicConverter
36+
{
37+
/**
38+
* Supports all formats of {@link org.eclipse.sisu.plexus.PlexusDateTypeConverter}
39+
*/
40+
private static final DateTimeFormatter PLEXUS_DATE_TIME_FORMATTER = DateTimeFormatter
41+
.ofPattern( "yyyy-MM-dd HH:mm:ss[[a][.S [a]]", Locale.US )
42+
.withZone( ZoneId.systemDefault() );
43+
44+
public boolean canConvert( final Class<?> type )
45+
{
46+
return Temporal.class.isAssignableFrom( type );
47+
}
48+
49+
@Override
50+
protected final Object fromString( final String str, final Class<?> type ) throws ComponentConfigurationException
51+
{
52+
return createTemporalFromString( str, type );
53+
}
54+
55+
private Temporal createTemporalFromString( String value, final Class<?> type )
56+
{
57+
TemporalAccessor temporalAccessor;
58+
try
59+
{
60+
temporalAccessor = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse( value );
61+
}
62+
catch ( DateTimeParseException e )
63+
{
64+
temporalAccessor = PLEXUS_DATE_TIME_FORMATTER.parse( value );
65+
}
66+
final Temporal temporal;
67+
if ( type.equals( LocalDate.class ) )
68+
{
69+
temporal = LocalDate.from( temporalAccessor );
70+
}
71+
else if ( type.equals( LocalDateTime.class ) )
72+
{
73+
temporal = LocalDateTime.from( temporalAccessor );
74+
}
75+
else if ( type.equals( LocalTime.class ) )
76+
{
77+
temporal = LocalTime.from( temporalAccessor );
78+
}
79+
else if ( type.equals( Instant.class ) )
80+
{
81+
temporal = Instant.from( temporalAccessor );
82+
}
83+
else if ( type.equals( OffsetDateTime.class ) )
84+
{
85+
temporal = ZonedDateTime.from( temporalAccessor ).toOffsetDateTime();
86+
}
87+
else if ( type.equals( OffsetTime.class ) )
88+
{
89+
temporal = ZonedDateTime.from( temporalAccessor ).toOffsetDateTime().toOffsetTime();
90+
}
91+
else if ( type.equals(ZonedDateTime.class ) )
92+
{
93+
temporal = ZonedDateTime.from( temporalAccessor );
94+
}
95+
else
96+
{
97+
throw new IllegalArgumentException( "Unsupported temporal type " + type );
98+
}
99+
return temporal;
100+
}
101+
}

org.eclipse.sisu.plexus/src/main/java/org/codehaus/plexus/component/configurator/converters/lookup/DefaultConverterLookup.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.codehaus.plexus.component.configurator.converters.basic.StringBufferConverter;
3434
import org.codehaus.plexus.component.configurator.converters.basic.StringBuilderConverter;
3535
import org.codehaus.plexus.component.configurator.converters.basic.StringConverter;
36+
import org.codehaus.plexus.component.configurator.converters.basic.TemporalConverter;
3637
import org.codehaus.plexus.component.configurator.converters.basic.UriConverter;
3738
import org.codehaus.plexus.component.configurator.converters.basic.UrlConverter;
3839
import org.codehaus.plexus.component.configurator.converters.composite.ArrayConverter;
@@ -73,6 +74,7 @@ public final class DefaultConverterLookup
7374
new ClassRealmConverter(), //
7475
new StringBufferConverter(), //
7576
new StringBuilderConverter(), //
77+
new TemporalConverter(), //
7678
new ObjectWithFieldsConverter() };
7779

7880
private final Map<Class<?>, ConfigurationConverter> lookupCache = //

org.eclipse.sisu.plexus/src/test/java/org/eclipse/sisu/plexus/BasicComponentConfiguratorTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
import java.nio.file.FileSystems;
1212
import java.nio.file.Path;
1313
import java.nio.file.Paths;
14+
import java.time.Instant;
15+
import java.time.LocalDate;
16+
import java.time.LocalDateTime;
17+
import java.time.LocalTime;
18+
import java.time.OffsetDateTime;
19+
import java.time.OffsetTime;
20+
import java.time.ZoneId;
21+
import java.time.ZoneOffset;
22+
import java.time.ZonedDateTime;
1423

1524
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
1625
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
@@ -25,6 +34,7 @@
2534
import org.junit.rules.TemporaryFolder;
2635

2736
import static org.junit.Assert.assertEquals;
37+
import static org.junit.Assert.assertThrows;
2838

2939
public class BasicComponentConfiguratorTest
3040
{
@@ -64,6 +74,68 @@ public void testTypeWithoutConverterButConstructorAcceptingString()
6474
assertEquals( "hello world", component.custom.toString() );
6575
}
6676

77+
@Test
78+
public void testTemporalConvertersWithoutMillisecondsAndOffset() throws ComponentConfigurationException
79+
{
80+
TemporalComponent component = new TemporalComponent();
81+
String dateString = "2023-01-02 03:04:05";
82+
configure( component,
83+
"localDateTime", dateString,
84+
"localDate", dateString,
85+
"localTime", dateString,
86+
"instant", dateString,
87+
"offsetDateTime", dateString,
88+
"offsetTime", dateString,
89+
"zonedDateTime", dateString );
90+
assertEquals( LocalDateTime.of(2023, 1, 2, 3, 4, 5, 0), component.localDateTime );
91+
assertEquals( LocalDate.of(2023, 1, 2), component.localDate );
92+
assertEquals( LocalTime.of(3, 4, 5, 0), component.localTime );
93+
ZoneOffset systemOffset = ZoneId.systemDefault().getRules().getOffset( component.localDateTime );
94+
assertEquals( OffsetDateTime.of( component.localDateTime, systemOffset).toInstant(), component.instant );
95+
assertEquals( OffsetDateTime.of( component.localDateTime, systemOffset), component.offsetDateTime );
96+
assertEquals( OffsetTime.of( component.localTime, systemOffset ), component.offsetTime );
97+
assertEquals( ZonedDateTime.of( component.localDateTime, ZoneId.systemDefault() ), component.zonedDateTime );
98+
}
99+
100+
@Test
101+
public void testTemporalConvertersWithISO8601StringWithOffset() throws ComponentConfigurationException
102+
{
103+
TemporalComponent component = new TemporalComponent();
104+
String dateString = "2023-01-02T03:04:05.000000900+02:30";
105+
configure( component,
106+
"localDateTime", dateString,
107+
"localDate", dateString,
108+
"localTime", dateString,
109+
"instant", dateString,
110+
"offsetDateTime", dateString,
111+
"offsetTime", dateString,
112+
"zonedDateTime", dateString );
113+
assertEquals( LocalDateTime.of(2023, 1, 2, 3, 4, 5, 900), component.localDateTime );
114+
assertEquals( LocalDate.of(2023, 1, 2), component.localDate );
115+
assertEquals( LocalTime.of(3, 4, 5, 900), component.localTime );
116+
ZoneOffset offset = ZoneOffset.ofHoursMinutes( 2, 30 );
117+
assertEquals( OffsetDateTime.of( component.localDateTime, offset).toInstant(), component.instant );
118+
assertEquals( OffsetDateTime.of( component.localDateTime, offset), component.offsetDateTime );
119+
assertEquals( OffsetTime.of( component.localTime, offset ), component.offsetTime );
120+
assertEquals( ZonedDateTime.of( component.localDateTime, offset ), component.zonedDateTime );
121+
}
122+
123+
@Test
124+
public void testTemporalConvertersWithInvalidString() throws ComponentConfigurationException
125+
{
126+
TemporalComponent component = new TemporalComponent();
127+
String dateString = "invalid";
128+
assertThrows( ComponentConfigurationException.class,
129+
() -> configure( component,
130+
"localDateTime", dateString,
131+
"localDate", dateString,
132+
"localTime", dateString,
133+
"instant", dateString,
134+
"offsetDateTime", dateString,
135+
"offsetTime", dateString,
136+
"zonedDateTime", dateString ) );
137+
}
138+
67139
private void configure( Object component, String... keysAndValues )
68140
throws ComponentConfigurationException
69141
{
@@ -136,4 +208,21 @@ static final class CustomTypeComponent
136208
{
137209
CustomType custom;
138210
}
211+
212+
static final class TemporalComponent
213+
{
214+
LocalDateTime localDateTime;
215+
216+
LocalDate localDate;
217+
218+
LocalTime localTime;
219+
220+
Instant instant;
221+
222+
OffsetDateTime offsetDateTime;
223+
224+
OffsetTime offsetTime;
225+
226+
ZonedDateTime zonedDateTime;
227+
}
139228
}

0 commit comments

Comments
 (0)