From 6cbdf7833e8cffdd50beee1dd897ba7eb7a33e2f Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Mon, 15 Apr 2024 09:04:18 -0400 Subject: [PATCH] - Support String containing a single UNICODE character to be convertible to char/Character - "true" (with quotes) as a String is also converted to true on String to boolean types. - Better error messages (comma handling on lists for Map conversions, highlighting what keys are valid) --- pom.xml | 2 +- .../util/convert/MapConversions.java | 6 +++- .../util/convert/StringConversions.java | 28 +++++++++++++++---- .../util/convert/ConverterTest.java | 6 ++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 004324d6..b126c279 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.cedarsoftware java-util bundle - 2.5.0 + 2.6.0-SNAPSHOT Java Utilities https://github.com/jdereg/java-util diff --git a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java index 061ff1d5..6d95fe0b 100644 --- a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java @@ -667,7 +667,11 @@ private static T fromMap(Object from, Converter converter, Class type, St builder.append(", "); } - builder.append("[value], or [_v] as keys with associated values."); + builder.append("[value]"); + if (keySets.length > 0) { + builder.append(","); + } + builder.append(" or [_v] as keys with associated values."); throw new IllegalArgumentException(builder.toString()); } } diff --git a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java index 3f95bb23..dbc40c4c 100644 --- a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java @@ -69,6 +69,7 @@ final class StringConversions { private static final BigDecimal bigDecimalMaxLong = BigDecimal.valueOf(Long.MAX_VALUE); private static final BigDecimal bigDecimalMinLong = BigDecimal.valueOf(Long.MIN_VALUE); private static final Pattern MM_DD = Pattern.compile("^(\\d{1,2}).(\\d{1,2})$"); + private static final Pattern allDigits = Pattern.compile("^\\d+$"); private StringConversions() {} @@ -198,7 +199,7 @@ static Boolean toBoolean(Object from, Converter converter) { } else if ("false".equals(str)) { return false; } - return "true".equalsIgnoreCase(str) || "t".equalsIgnoreCase(str) || "1".equals(str) || "y".equalsIgnoreCase(str); + return "true".equalsIgnoreCase(str) || "t".equalsIgnoreCase(str) || "1".equals(str) || "y".equalsIgnoreCase(str) || "\"true\"".equalsIgnoreCase(str); } static char toCharacter(Object from, Converter converter) { @@ -209,12 +210,27 @@ static char toCharacter(Object from, Converter converter) { if (str.length() == 1) { return str.charAt(0); } - // Treat as a String number, like "65" = 'A' - try { - return (char) Integer.parseInt(str.trim()); - } catch (Exception e) { - throw new IllegalArgumentException("Unable to parse '" + from + "' as a Character.", e); + + Matcher matcher = allDigits.matcher(str); + boolean isAllDigits = matcher.matches(); + if (isAllDigits) { + try { // Treat as a String number, like "65" = 'A' + return (char) Integer.parseInt(str.trim()); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to parse '" + from + "' as a Character.", e); + } + } + + char result = parseUnicodeEscape(str); + return result; + } + + private static char parseUnicodeEscape(String unicodeStr) throws IllegalArgumentException { + if (!unicodeStr.startsWith("\\u") || unicodeStr.length() != 6) { + throw new IllegalArgumentException("Invalid Unicode escape sequence: " + unicodeStr); } + int codePoint = Integer.parseInt(unicodeStr.substring(2), 16); + return (char) codePoint; } static BigInteger toBigInteger(Object from, Converter converter) { diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java index b98128f7..93144d57 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java @@ -2795,7 +2795,7 @@ void testMapToAtomicBoolean() map.clear(); assertThatThrownBy(() -> this.converter.convert(map, AtomicBoolean.class)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Map to 'AtomicBoolean' the map must include: [value], or [_v] as keys with associated values"); + .hasMessageContaining("Map to 'AtomicBoolean' the map must include: [value] or [_v] as keys with associated values"); } @Test @@ -2818,7 +2818,7 @@ void testMapToAtomicInteger() map.clear(); assertThatThrownBy(() -> this.converter.convert(map, AtomicInteger.class)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Map to 'AtomicInteger' the map must include: [value], or [_v] as keys with associated values"); + .hasMessageContaining("Map to 'AtomicInteger' the map must include: [value] or [_v] as keys with associated values"); } @Test @@ -2841,7 +2841,7 @@ void testMapToAtomicLong() map.clear(); assertThatThrownBy(() -> this.converter.convert(map, AtomicLong.class)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Map to 'AtomicLong' the map must include: [value], or [_v] as keys with associated values"); + .hasMessageContaining("Map to 'AtomicLong' the map must include: [value] or [_v] as keys with associated values"); } @ParameterizedTest