From f50005515f64a5d97cddf9a06c100d672fd7f80b Mon Sep 17 00:00:00 2001 From: Rebeca Gallardo Date: Wed, 22 May 2024 11:42:22 -0700 Subject: [PATCH] Be more lenient about number parsing * Accept number constants with whitespace. * Accept a trailing L/l in long constants. Fixes: #719 --- .../DefaultTypeConverterFactory.java | 71 +++++++++++++++---- .../netflix/archaius/DefaultDecoderTest.java | 16 ++++- 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/archaius2-core/src/main/java/com/netflix/archaius/converters/DefaultTypeConverterFactory.java b/archaius2-core/src/main/java/com/netflix/archaius/converters/DefaultTypeConverterFactory.java index 17522beb..2fa15f4b 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/converters/DefaultTypeConverterFactory.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/converters/DefaultTypeConverterFactory.java @@ -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); @@ -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); @@ -103,4 +103,45 @@ public Optional> 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)); + } + } } diff --git a/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java b/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java index caec3c28..e77d93a4 100644 --- a/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java +++ b/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java @@ -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 @@ -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 @@ -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")); @@ -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 }