Skip to content

Commit

Permalink
Be more lenient about number parsing
Browse files Browse the repository at this point in the history
* Accept number constants with whitespace.
* Accept a trailing L/l in long constants. Fixes: #719
  • Loading branch information
rgallardo-netflix committed May 22, 2024
1 parent 19dfd7e commit f500055
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,22 @@ private DefaultTypeConverterFactory() {
converters.put(String.class, Function.identity()::apply);
converters.put(boolean.class, DefaultTypeConverterFactory::convertBoolean);
converters.put(Boolean.class, DefaultTypeConverterFactory::convertBoolean);
converters.put(Integer.class, Integer::valueOf);
converters.put(int.class, Integer::valueOf);
converters.put(long.class, Long::valueOf);
converters.put(Long.class, Long::valueOf);
converters.put(short.class, Short::valueOf);
converters.put(Short.class, Short::valueOf);
converters.put(byte.class, Byte::valueOf);
converters.put(Byte.class, Byte::valueOf);
converters.put(double.class, Double::valueOf);
converters.put(Double.class, Double::valueOf);
converters.put(float.class, Float::valueOf);
converters.put(Float.class, Float::valueOf);
converters.put(Integer.class, Lenient::parseInt);
converters.put(int.class, Lenient::parseInt);
converters.put(long.class, Lenient::parseLong);
converters.put(Long.class, Lenient::parseLong);
converters.put(short.class, Lenient::parseShort);
converters.put(Short.class, Lenient::parseShort);
converters.put(byte.class, Lenient::parseByte);
converters.put(Byte.class, Lenient::parseByte);
converters.put(double.class, Lenient::parseDouble);
converters.put(Double.class, Lenient::parseDouble);
converters.put(float.class, Lenient::parseFloat);
converters.put(Float.class, Lenient::parseFloat);
converters.put(BigInteger.class, BigInteger::new);
converters.put(BigDecimal.class, BigDecimal::new);
converters.put(AtomicInteger.class, v -> new AtomicInteger(Integer.parseInt(v)));
converters.put(AtomicLong.class, v -> new AtomicLong(Long.parseLong(v)));
converters.put(AtomicInteger.class, v -> new AtomicInteger(Lenient.parseInt(v)));
converters.put(AtomicLong.class, v -> new AtomicLong(Lenient.parseLong(v)));
converters.put(Duration.class, Duration::parse);
converters.put(Period.class, Period::parse);
converters.put(LocalDateTime.class, LocalDateTime::parse);
Expand All @@ -76,7 +76,7 @@ private DefaultTypeConverterFactory() {
converters.put(OffsetTime.class, OffsetTime::parse);
converters.put(ZonedDateTime.class, ZonedDateTime::parse);
converters.put(Instant.class, v -> Instant.from(OffsetDateTime.parse(v)));
converters.put(Date.class, v -> new Date(Long.parseLong(v)));
converters.put(Date.class, v -> new Date(Lenient.parseLong(v)));
converters.put(Currency.class, Currency::getInstance);
converters.put(URI.class, URI::create);
converters.put(Locale.class, Locale::forLanguageTag);
Expand All @@ -103,4 +103,45 @@ public Optional<TypeConverter<?>> get(Type type, TypeConverter.Registry registry
}
return Optional.empty();
}

/** A collection of lenient number parsers that allow whitespace and trailing 'L' or 'l' in long values */
private static final class Lenient {
private static String maybeTrim(String s) {
// The way these are called, we'll never get a null. In any case, we pass it through, to ensure that
// the exception thrown remains the same as whatever the JDK's parse***() methods throw.
return s != null ? s.trim() : null;
}

private static long parseLong(String s) throws NumberFormatException {
s = maybeTrim(s);
// Also allow trailing 'L' or 'l' in long values
if (s != null) {
if (s.endsWith("L") || s.endsWith("l")) {
s = s.substring(0, s.length() - 1);
}
}

return Long.parseLong(s);
}

private static int parseInt(String s) throws NumberFormatException {
return Integer.parseInt(maybeTrim(s));
}

private static short parseShort(String s) throws NumberFormatException {
return Short.parseShort(maybeTrim(s));
}

private static byte parseByte(String s) throws NumberFormatException {
return Byte.parseByte(maybeTrim(s));
}

private static double parseDouble(String s) throws NumberFormatException {
return Double.parseDouble(maybeTrim(s));
}

private static float parseFloat(String s) throws NumberFormatException {
return Float.parseFloat(maybeTrim(s));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ public void testJavaNumbers() {
assertEquals(BigDecimal.valueOf(Double.MAX_VALUE), decoder.decode(BigDecimal.class, String.valueOf(Double.MAX_VALUE)));
assertEquals(Integer.MAX_VALUE, decoder.decode(AtomicInteger.class, String.valueOf(Integer.MAX_VALUE)).get());
assertEquals(Long.MAX_VALUE, decoder.decode(AtomicLong.class, String.valueOf(Long.MAX_VALUE)).get());

// Verify lenient decoding
assertEquals(123L, decoder.decode(long.class, "123L"));
assertEquals(123L, decoder.decode(long.class, "123l"));
assertEquals(123L, decoder.decode(long.class, "\t\t\n 123L \n\n ")); /// Mixed types of whitespace AND trailing L

assertEquals(123, decoder.decode(int.class, " 123 "));
assertEquals(123.456, decoder.decode(double.class, " 123.456 "));
}

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

@Test
Expand All @@ -117,6 +127,8 @@ public void testCollections() {
assertEquals(Collections.emptyList(), decoder.decode(listOfIntegerType, ""));
assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), decoder.decode(listOfIntegerType, "1,2,3,4,5,6"));
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), decoder.decode(collectionOfLongType, "1,2,3,4,5,6"));
/// We should be lenient with whitespace and trailing L or l
assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), decoder.decode(collectionOfLongType, "1L, 2 , 3l ,4L , 5\t, \n 6 "));
assertEquals(Collections.singleton(2L), decoder.decode(setOfLongType, "2,2,2,2"));
assertEquals(Collections.emptyMap(), decoder.decode(mapofStringToIntegerType, ""));
assertEquals(Collections.singletonMap("key", 12345), decoder.decode(mapofStringToIntegerType, "key=12345"));
Expand All @@ -134,6 +146,8 @@ public void testArrays() {
assertArrayEquals(new long[] {1L, 2L, 3L, 4L, 5L}, decoder.decode(long[].class, "1,2,3,4,5"));
assertArrayEquals(new Long[0], decoder.decode(Long[].class, ""));
assertArrayEquals(new long[0], decoder.decode(long[].class, ""));
/// We should be lenient with whitespace and trailing L or l
assertArrayEquals(new long[] {1L, 2L, 3L, 4L, 5L, 6L}, decoder.decode(long[].class, "1L, 2 , 3l ,4L , 5\t, \n 6 "));
}

enum TestEnumType { FOO, BAR, BAZ }
Expand Down

0 comments on commit f500055

Please sign in to comment.