Skip to content

Commit f9e1bfb

Browse files
committed
feat: updated existing converters
1 parent 3f249da commit f9e1bfb

File tree

10 files changed

+248
-20
lines changed

10 files changed

+248
-20
lines changed

src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.nio.file.Path;
3131
import java.sql.Timestamp;
3232
import java.time.Duration;
33+
import java.time.Instant;
3334
import java.time.LocalDate;
3435
import java.time.LocalDateTime;
3536
import java.time.LocalTime;
@@ -68,6 +69,7 @@
6869
import org.apache.commons.beanutils2.converters.FileConverter;
6970
import org.apache.commons.beanutils2.converters.FloatConverter;
7071
import org.apache.commons.beanutils2.converters.InetAddressConverter;
72+
import org.apache.commons.beanutils2.converters.InstantConverter;
7173
import org.apache.commons.beanutils2.converters.IntegerConverter;
7274
import org.apache.commons.beanutils2.converters.LocalDateConverter;
7375
import org.apache.commons.beanutils2.converters.LocalDateTimeConverter;
@@ -144,6 +146,7 @@
144146
* <li>java.sql.Date (no default value)</li>
145147
* <li>java.sql.Time (no default value)</li>
146148
* <li>java.sql.Timestamp (no default value)</li>
149+
* <li>java.time.Instant (no default value)</li>
147150
* <li>java.time.LocalDate (no default value)</li>
148151
* <li>java.time.LocalDateTime (no default value)</li>
149152
* <li>java.time.LocalTime (no default value)</li>
@@ -500,6 +503,7 @@ private void registerArrays(final boolean throwException, final int defaultArray
500503
registerArrayConverter(Dimension.class, new DimensionConverter(), throwException, defaultArraySize);
501504
registerArrayConverter(File.class, new FileConverter(), throwException, defaultArraySize);
502505
registerArrayConverter(InetAddress.class, new InetAddressConverter(), throwException, defaultArraySize);
506+
registerArrayConverter(Instant.class, new InstantConverter(), throwException, defaultArraySize);
503507
registerArrayConverter(Path.class, new PathConverter(), throwException, defaultArraySize);
504508
registerArrayConverter(java.sql.Date.class, new SqlDateConverter(), throwException, defaultArraySize);
505509
registerArrayConverter(java.sql.Time.class, new SqlTimeConverter(), throwException, defaultArraySize);
@@ -536,6 +540,7 @@ private void registerArrays(final boolean throwException, final int defaultArray
536540
* <li>{@code java.util.Date.class} - {@link DateConverter}</li>
537541
* <li>{@code java.util.Calendar.class} - {@link CalendarConverter}</li>
538542
* <li>{@code File.class} - {@link FileConverter}</li>
543+
* <li>{@code Instant.class} - {@link InstantConverter}</li>
539544
* <li>{@code Path.class} - {@link PathConverter}</li>
540545
* <li>{@code java.sql.Date.class} - {@link SqlDateConverter}</li>
541546
* <li>{@code java.sql.Time.class} - {@link SqlTimeConverter}</li>
@@ -571,6 +576,7 @@ private void registerOther(final boolean throwException) {
571576
register(Calendar.class, throwException ? new CalendarConverter() : new CalendarConverter(null));
572577
register(File.class, throwException ? new FileConverter() : new FileConverter(null));
573578
register(InetAddress.class, throwException ? new InetAddressConverter() : new InetAddressConverter(null));
579+
register(Instant.class, throwException ? new InstantConverter() : new InstantConverter(null));
574580
register(Path.class, throwException ? new PathConverter() : new PathConverter(null));
575581
register(java.sql.Date.class, throwException ? new SqlDateConverter() : new SqlDateConverter(null));
576582
register(java.sql.Time.class, throwException ? new SqlTimeConverter() : new SqlTimeConverter(null));

src/main/java/org/apache/commons/beanutils2/converters/CharacterConverter.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,13 @@ protected String convertToString(final Object value) {
7979
@Override
8080
protected <T> T convertToType(final Class<T> type, final Object value) throws Exception {
8181
if (Character.class.equals(type) || Character.TYPE.equals(type)) {
82-
return type.cast(Character.valueOf(value.toString().charAt(0)));
82+
final String stringValue = toString(value);
83+
84+
if (stringValue.isEmpty()) {
85+
throw new IllegalArgumentException("Value must not be empty");
86+
}
87+
88+
return type.cast(Character.valueOf(stringValue.charAt(0)));
8389
}
8490

8591
throw conversionException(type, value);

src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java

+22
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.time.OffsetDateTime;
2626
import java.time.ZoneId;
2727
import java.time.ZonedDateTime;
28+
import java.time.format.DateTimeParseException;
2829
import java.time.temporal.TemporalAccessor;
2930
import java.util.Calendar;
3031
import java.util.Date;
@@ -135,6 +136,8 @@ protected String convertToString(final Object value) {
135136
} else if (value instanceof TemporalAccessor) {
136137
// Backstop for other TemporalAccessor implementations.
137138
date = Date.from(Instant.from((TemporalAccessor) value));
139+
} else if (value instanceof Instant) {
140+
date = Date.from((Instant) value);
138141
}
139142

140143
String result = null;
@@ -169,6 +172,7 @@ protected String convertToString(final Object value) {
169172
* <li>{@link java.time.LocalDate}</li>
170173
* <li>{@link java.time.LocalDateTime}</li>
171174
* <li>{@link java.time.OffsetDateTime}</li>
175+
* <li>{@link java.time.Instant}</li>
172176
* <li>{@link java.time.ZonedDateTime}</li>
173177
* <li>{@link java.sql.Date}</li>
174178
* <li>{@link java.sql.Time}</li>
@@ -247,6 +251,11 @@ protected <T> T convertToType(final Class<T> targetType, final Object value) thr
247251
return toDate(targetType, date.toInstant().toEpochMilli());
248252
}
249253

254+
if (value instanceof Instant) {
255+
final Instant date = (Instant) value;
256+
return toDate(targetType, date.toEpochMilli());
257+
}
258+
250259
// Convert all other types to String & handle
251260
final String stringValue = toTrim(value);
252261
if (stringValue.isEmpty()) {
@@ -555,6 +564,10 @@ private <T> T toDate(final Class<T> type, final long value) {
555564
return type.cast(offsetDateTime);
556565
}
557566

567+
if (type.equals(Instant.class)) {
568+
return type.cast(Instant.ofEpochMilli(value));
569+
}
570+
558571
// java.util.Calendar
559572
if (type.equals(Calendar.class)) {
560573
Calendar calendar = null;
@@ -587,6 +600,7 @@ private <T> T toDate(final Class<T> type, final long value) {
587600
* <li>{@link java.sql.Date}</li>
588601
* <li>{@link java.sql.Time}</li>
589602
* <li>{@link java.sql.Timestamp}</li>
603+
* <li>{@link java.time.Instant}</li>
590604
* </ul>
591605
* <p>
592606
* <strong>N.B.</strong> No default String conversion mechanism is provided for {@link java.util.Date} and {@link java.util.Calendar} type.
@@ -624,6 +638,14 @@ private <T> T toDate(final Class<T> type, final String value) {
624638
}
625639
}
626640

641+
if (type.equals(Instant.class)) {
642+
try {
643+
return type.cast(Instant.parse(value));
644+
} catch (final DateTimeParseException ex) {
645+
throw new ConversionException("String must be in ISO-8601 format to create a java.time.Instant");
646+
}
647+
}
648+
627649
final String msg = toString(getClass()) + " does not support default String to '" + toString(type) + "' conversion.";
628650
if (log().isWarnEnabled()) {
629651
log().warn(" " + msg);

src/main/java/org/apache/commons/beanutils2/converters/DurationConverter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public DurationConverter(final Duration defaultValue) {
5959
@Override
6060
protected <T> T convertToType(final Class<T> type, final Object value) throws Throwable {
6161
if (Duration.class.equals(type)) {
62-
return type.cast(Duration.parse(String.valueOf(value)));
62+
return type.cast(Duration.parse(toString(value)));
6363
}
6464

6565
throw conversionException(type, value);

src/main/java/org/apache/commons/beanutils2/converters/EnumConverter.java

+37-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
*/
1717
package org.apache.commons.beanutils2.converters;
1818

19+
import java.util.regex.Matcher;
20+
import java.util.regex.Pattern;
21+
1922
/**
2023
* {@link org.apache.commons.beanutils2.Converter} implementation that handles conversion to and from <strong>java.lang.Enum</strong> objects.
2124
* <p>
@@ -28,6 +31,11 @@
2831
*/
2932
public final class EnumConverter<E extends Enum<E>> extends AbstractConverter<Enum<E>> {
3033

34+
/** Matches if a given input is an enum string. */
35+
private static final Pattern ENUM_PATTERN = Pattern.compile(
36+
"((?:[a-z\\d.]+)*)\\.([A-Za-z\\d]+)[#.]([A-Z\\d_]+)"
37+
);
38+
3139
/**
3240
* Constructs a <strong>java.lang.Enum</strong> <em>Converter</em> that throws a {@code ConversionException} if an error occurs.
3341
*/
@@ -59,15 +67,37 @@ public EnumConverter(final Enum<E> defaultValue) {
5967
@Override
6068
protected <R> R convertToType(final Class<R> type, final Object value) throws Throwable {
6169
if (Enum.class.isAssignableFrom(type)) {
62-
final String enumValue = String.valueOf(value);
63-
final R[] constants = type.getEnumConstants();
64-
if (constants == null) {
65-
throw conversionException(type, value);
70+
final String stringValue = toString(value);
71+
72+
try {
73+
return type.cast((Enum) Enum.valueOf((Class) type, stringValue));
74+
} catch (IllegalArgumentException ex) {
75+
// Continue to check fully qualified name.
76+
}
77+
78+
Matcher matcher = ENUM_PATTERN.matcher(stringValue);
79+
80+
if (!matcher.matches()) {
81+
throw new IllegalArgumentException(
82+
"Value doesn't follow Enum naming convention, expecting value like: java.time.DayOfWeek.MONDAY");
6683
}
67-
for (final R candidate : constants) {
68-
if (((Enum) candidate).name().equalsIgnoreCase(enumValue)) {
69-
return candidate;
84+
85+
String className = matcher.group(1) + "." + matcher.group(2);
86+
87+
try {
88+
Class classForName = Class.forName(className);
89+
90+
if (!classForName.isEnum()) {
91+
throw new IllegalArgumentException("Value isn't an enumerated type.");
7092
}
93+
94+
if (!type.isAssignableFrom(classForName)) {
95+
throw new IllegalArgumentException("Class is not the required type.");
96+
}
97+
98+
return type.cast((Enum) Enum.valueOf(classForName, matcher.group(3)));
99+
} catch (ClassNotFoundException ex) {
100+
throw new IllegalArgumentException("Class \"" + className + "\" doesn't exist.", ex);
71101
}
72102
}
73103

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.beanutils2.converters;
18+
19+
import java.time.Instant;
20+
21+
/**
22+
* {@link org.apache.commons.beanutils2.Converter} implementation that handles conversion to and from {@link Instant} objects.
23+
* <p>
24+
* Can be configured to either return a <i>default value</i> or throw a {@code ConversionException} if a conversion error occurs.
25+
* </p>
26+
*
27+
* @since 2.0
28+
* @see Instant
29+
*/
30+
public final class InstantConverter extends DateTimeConverter<Instant> {
31+
32+
/**
33+
* Constructs a {@link Instant} <i>Converter</i> that throws a {@code ConversionException} if an error occurs.
34+
*/
35+
public InstantConverter() {
36+
super();
37+
}
38+
39+
/**
40+
* Constructs a {@link Instant} <i>Converter</i> that returns a default value if an error occurs.
41+
*
42+
* @param defaultValue The default value to be returned if the value to be converted is missing or an error occurs converting the value.
43+
*/
44+
public InstantConverter(final Instant defaultValue) {
45+
super(defaultValue);
46+
}
47+
48+
/**
49+
* Gets the default type this {@code Converter} handles.
50+
*
51+
* @return Default type this {@code Converter} handles.
52+
*/
53+
@Override
54+
protected Class<Instant> getDefaultType() {
55+
return Instant.class;
56+
}
57+
}

src/main/java/org/apache/commons/beanutils2/converters/PeriodConverter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public PeriodConverter(final Period defaultValue) {
5959
@Override
6060
protected <T> T convertToType(final Class<T> type, final Object value) throws Throwable {
6161
if (Period.class.equals(type)) {
62-
return type.cast(Period.parse(String.valueOf(value)));
62+
return type.cast(Period.parse(toString(value)));
6363
}
6464

6565
throw conversionException(type, value);

src/test/java/org/apache/commons/beanutils2/converters/CharacterConverterTest.java

+4-10
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,23 @@
3030
*/
3131
public class CharacterConverterTest {
3232

33-
/** Sets Up */
33+
private Converter<Character> converter;
34+
3435
@BeforeEach
3536
public void setUp() throws Exception {
37+
converter = new CharacterConverter();
3638
}
3739

38-
/** Tear Down */
3940
@AfterEach
4041
public void tearDown() throws Exception {
42+
converter = null;
4143
}
4244

4345
/**
4446
* Tests whether the primitive char class can be passed as target type.
4547
*/
4648
@Test
4749
public void testConvertToChar() {
48-
final Converter<Character> converter = new CharacterConverter();
4950
assertEquals(Character.valueOf('F'), converter.convert(Character.TYPE, "FOO"), "Wrong result");
5051
}
5152

@@ -54,7 +55,6 @@ public void testConvertToChar() {
5455
*/
5556
@Test
5657
public void testConvertToCharacter() {
57-
final Converter<Character> converter = new CharacterConverter();
5858
assertEquals(Character.valueOf('N'), converter.convert(Character.class, Character.valueOf('N')), "Character Test");
5959
assertEquals(Character.valueOf('F'), converter.convert(Character.class, "FOO"), "String Test");
6060
assertEquals(Character.valueOf('3'), converter.convert(Character.class, Integer.valueOf(321)), "Integer Test");
@@ -65,7 +65,6 @@ public void testConvertToCharacter() {
6565
*/
6666
@Test
6767
public void testConvertToCharacterNullNoDefault() {
68-
final Converter<Character> converter = new CharacterConverter();
6968
assertThrows(ConversionException.class, () -> converter.convert(Character.class, null));
7069
}
7170

@@ -75,8 +74,6 @@ public void testConvertToCharacterNullNoDefault() {
7574
@Test
7675
@SuppressWarnings("unchecked") // testing raw conversion
7776
public void testConvertToString() {
78-
79-
final Converter<Character> converter = new CharacterConverter();
8077
@SuppressWarnings("rawtypes")
8178
final Converter raw = converter;
8279

@@ -90,10 +87,7 @@ public void testConvertToString() {
9087
* Tries a conversion to an unsupported type.
9188
*/
9289
@Test
93-
@SuppressWarnings("unchecked") // tests failure so allow mismatch
9490
public void testConvertToUnsupportedType() {
95-
@SuppressWarnings("rawtypes") // tests failure so allow mismatch
96-
final Converter converter = new CharacterConverter();
9791
assertThrows(ConversionException.class, () -> converter.convert(Integer.class, "Test"));
9892
}
9993

src/test/java/org/apache/commons/beanutils2/converters/EnumConverterTest.java

+37
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import static org.junit.jupiter.api.Assertions.assertEquals;
2121
import static org.junit.jupiter.api.Assertions.assertThrows;
2222

23+
import java.time.DayOfWeek;
24+
import java.util.concurrent.TimeUnit;
25+
2326
import org.apache.commons.beanutils2.ConversionException;
2427
import org.apache.commons.beanutils2.Converter;
2528
import org.junit.jupiter.api.AfterEach;
@@ -79,4 +82,38 @@ public void testSimpleConversion() throws Exception {
7982
public void testUnsupportedType() {
8083
assertThrows(ConversionException.class, () -> converter.convert(Integer.class, "http://www.apache.org"));
8184
}
85+
86+
@Test
87+
public void testConvertTimeUnit() {
88+
final TimeUnit expected = TimeUnit.NANOSECONDS;
89+
final Enum actual = converter.convert(Enum.class, "java.util.concurrent.TimeUnit.NANOSECONDS");
90+
assertEquals(expected, actual);
91+
}
92+
93+
@Test
94+
public void testConvertDayOfWeek() {
95+
final DayOfWeek expected = DayOfWeek.MONDAY;
96+
final DayOfWeek actual = converter.convert(DayOfWeek.class, "java.time.DayOfWeek#MONDAY");
97+
assertEquals(expected, actual);
98+
}
99+
100+
@Test
101+
public void testConvertMismatchingEnumType() {
102+
assertThrows(ConversionException.class, () -> converter.convert(TimeUnit.class, "java.time.DayOfWeek#MONDAY"));
103+
}
104+
105+
@Test
106+
public void testBrokenNamingConvention() {
107+
assertThrows(ConversionException.class, () -> converter.convert(Enum.class, "JAVA-TIME-DAYOFWEEK#MONDAY"));
108+
}
109+
110+
@Test
111+
public void testNonEnumClasses() {
112+
assertThrows(ConversionException.class, () -> converter.convert(Enum.class, "java.lang.String#MONDAY"));
113+
}
114+
115+
@Test
116+
public void testNonExistingClasses() {
117+
assertThrows(ConversionException.class, () -> converter.convert(Enum.class, "java.lang.does.not.exist#MONDAY"));
118+
}
82119
}

0 commit comments

Comments
 (0)