Skip to content

Commit 424287b

Browse files
Merge pull request #725 from Netflix/lenient-number-parsers
Be more lenient about number parsing
2 parents 19dfd7e + f500055 commit 424287b

File tree

2 files changed

+71
-16
lines changed

2 files changed

+71
-16
lines changed

archaius2-core/src/main/java/com/netflix/archaius/converters/DefaultTypeConverterFactory.java

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,22 @@ private DefaultTypeConverterFactory() {
5151
converters.put(String.class, Function.identity()::apply);
5252
converters.put(boolean.class, DefaultTypeConverterFactory::convertBoolean);
5353
converters.put(Boolean.class, DefaultTypeConverterFactory::convertBoolean);
54-
converters.put(Integer.class, Integer::valueOf);
55-
converters.put(int.class, Integer::valueOf);
56-
converters.put(long.class, Long::valueOf);
57-
converters.put(Long.class, Long::valueOf);
58-
converters.put(short.class, Short::valueOf);
59-
converters.put(Short.class, Short::valueOf);
60-
converters.put(byte.class, Byte::valueOf);
61-
converters.put(Byte.class, Byte::valueOf);
62-
converters.put(double.class, Double::valueOf);
63-
converters.put(Double.class, Double::valueOf);
64-
converters.put(float.class, Float::valueOf);
65-
converters.put(Float.class, Float::valueOf);
54+
converters.put(Integer.class, Lenient::parseInt);
55+
converters.put(int.class, Lenient::parseInt);
56+
converters.put(long.class, Lenient::parseLong);
57+
converters.put(Long.class, Lenient::parseLong);
58+
converters.put(short.class, Lenient::parseShort);
59+
converters.put(Short.class, Lenient::parseShort);
60+
converters.put(byte.class, Lenient::parseByte);
61+
converters.put(Byte.class, Lenient::parseByte);
62+
converters.put(double.class, Lenient::parseDouble);
63+
converters.put(Double.class, Lenient::parseDouble);
64+
converters.put(float.class, Lenient::parseFloat);
65+
converters.put(Float.class, Lenient::parseFloat);
6666
converters.put(BigInteger.class, BigInteger::new);
6767
converters.put(BigDecimal.class, BigDecimal::new);
68-
converters.put(AtomicInteger.class, v -> new AtomicInteger(Integer.parseInt(v)));
69-
converters.put(AtomicLong.class, v -> new AtomicLong(Long.parseLong(v)));
68+
converters.put(AtomicInteger.class, v -> new AtomicInteger(Lenient.parseInt(v)));
69+
converters.put(AtomicLong.class, v -> new AtomicLong(Lenient.parseLong(v)));
7070
converters.put(Duration.class, Duration::parse);
7171
converters.put(Period.class, Period::parse);
7272
converters.put(LocalDateTime.class, LocalDateTime::parse);
@@ -76,7 +76,7 @@ private DefaultTypeConverterFactory() {
7676
converters.put(OffsetTime.class, OffsetTime::parse);
7777
converters.put(ZonedDateTime.class, ZonedDateTime::parse);
7878
converters.put(Instant.class, v -> Instant.from(OffsetDateTime.parse(v)));
79-
converters.put(Date.class, v -> new Date(Long.parseLong(v)));
79+
converters.put(Date.class, v -> new Date(Lenient.parseLong(v)));
8080
converters.put(Currency.class, Currency::getInstance);
8181
converters.put(URI.class, URI::create);
8282
converters.put(Locale.class, Locale::forLanguageTag);
@@ -103,4 +103,45 @@ public Optional<TypeConverter<?>> get(Type type, TypeConverter.Registry registry
103103
}
104104
return Optional.empty();
105105
}
106+
107+
/** A collection of lenient number parsers that allow whitespace and trailing 'L' or 'l' in long values */
108+
private static final class Lenient {
109+
private static String maybeTrim(String s) {
110+
// The way these are called, we'll never get a null. In any case, we pass it through, to ensure that
111+
// the exception thrown remains the same as whatever the JDK's parse***() methods throw.
112+
return s != null ? s.trim() : null;
113+
}
114+
115+
private static long parseLong(String s) throws NumberFormatException {
116+
s = maybeTrim(s);
117+
// Also allow trailing 'L' or 'l' in long values
118+
if (s != null) {
119+
if (s.endsWith("L") || s.endsWith("l")) {
120+
s = s.substring(0, s.length() - 1);
121+
}
122+
}
123+
124+
return Long.parseLong(s);
125+
}
126+
127+
private static int parseInt(String s) throws NumberFormatException {
128+
return Integer.parseInt(maybeTrim(s));
129+
}
130+
131+
private static short parseShort(String s) throws NumberFormatException {
132+
return Short.parseShort(maybeTrim(s));
133+
}
134+
135+
private static byte parseByte(String s) throws NumberFormatException {
136+
return Byte.parseByte(maybeTrim(s));
137+
}
138+
139+
private static double parseDouble(String s) throws NumberFormatException {
140+
return Double.parseDouble(maybeTrim(s));
141+
}
142+
143+
private static float parseFloat(String s) throws NumberFormatException {
144+
return Float.parseFloat(maybeTrim(s));
145+
}
146+
}
106147
}

archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ public void testJavaNumbers() {
8282
assertEquals(BigDecimal.valueOf(Double.MAX_VALUE), decoder.decode(BigDecimal.class, String.valueOf(Double.MAX_VALUE)));
8383
assertEquals(Integer.MAX_VALUE, decoder.decode(AtomicInteger.class, String.valueOf(Integer.MAX_VALUE)).get());
8484
assertEquals(Long.MAX_VALUE, decoder.decode(AtomicLong.class, String.valueOf(Long.MAX_VALUE)).get());
85+
86+
// Verify lenient decoding
87+
assertEquals(123L, decoder.decode(long.class, "123L"));
88+
assertEquals(123L, decoder.decode(long.class, "123l"));
89+
assertEquals(123L, decoder.decode(long.class, "\t\t\n 123L \n\n ")); /// Mixed types of whitespace AND trailing L
90+
91+
assertEquals(123, decoder.decode(int.class, " 123 "));
92+
assertEquals(123.456, decoder.decode(double.class, " 123.456 "));
8593
}
8694

8795
@Test
@@ -98,7 +106,9 @@ public void testJavaDateTime() {
98106
assertEquals(LocalTime.parse("10:15:30"), decoder.decode(LocalTime.class, "10:15:30"));
99107
assertEquals(Instant.from(OffsetDateTime.parse("2016-08-03T10:15:30+07:00")), decoder.decode(Instant.class, "2016-08-03T10:15:30+07:00"));
100108
Date newDate = new Date();
101-
assertEquals(newDate, decoder.decode(Date.class, String.valueOf(newDate.getTime())));
109+
String encodedDate = String.valueOf(newDate.getTime());
110+
assertEquals(newDate, decoder.decode(Date.class, encodedDate));
111+
assertEquals(newDate, decoder.decode(Date.class, " " + encodedDate + " "), "date decoding should be lenient of whitespace");
102112
}
103113

104114
@Test
@@ -117,6 +127,8 @@ public void testCollections() {
117127
assertEquals(Collections.emptyList(), decoder.decode(listOfIntegerType, ""));
118128
assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), decoder.decode(listOfIntegerType, "1,2,3,4,5,6"));
119129
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), decoder.decode(collectionOfLongType, "1,2,3,4,5,6"));
130+
/// We should be lenient with whitespace and trailing L or l
131+
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), decoder.decode(collectionOfLongType, "1L, 2 , 3l ,4L , 5\t, \n 6 "));
120132
assertEquals(Collections.singleton(2L), decoder.decode(setOfLongType, "2,2,2,2"));
121133
assertEquals(Collections.emptyMap(), decoder.decode(mapofStringToIntegerType, ""));
122134
assertEquals(Collections.singletonMap("key", 12345), decoder.decode(mapofStringToIntegerType, "key=12345"));
@@ -134,6 +146,8 @@ public void testArrays() {
134146
assertArrayEquals(new long[] {1L, 2L, 3L, 4L, 5L}, decoder.decode(long[].class, "1,2,3,4,5"));
135147
assertArrayEquals(new Long[0], decoder.decode(Long[].class, ""));
136148
assertArrayEquals(new long[0], decoder.decode(long[].class, ""));
149+
/// We should be lenient with whitespace and trailing L or l
150+
assertArrayEquals(new long[] {1L, 2L, 3L, 4L, 5L, 6L}, decoder.decode(long[].class, "1L, 2 , 3l ,4L , 5\t, \n 6 "));
137151
}
138152

139153
enum TestEnumType { FOO, BAR, BAZ }

0 commit comments

Comments
 (0)