From e89994da157f2ee2106727cc22e1d7a67e616d21 Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Sat, 3 Feb 2024 18:41:28 -0500 Subject: [PATCH] Unsupported conversions can be explicitly added to prevent an inherited type (like Number) from allowing all derivate types to become supported conversions. For example, Long is a Number and Long can convert to/from a Date. However, Byte is a Number, but it should not convert to/from a Date. By adding an explicit UNSUPPORTED conversion, it will prevent it. Furthermore, the allSupported/getSupported conversions will filter these out so they are not adverstised as available conversions. --- .../com/cedarsoftware/util/Converter.java | 6 ++ .../cedarsoftware/util/convert/Converter.java | 77 +++++++++++++++---- .../util/convert/ConverterTest.java | 35 +++++---- 3 files changed, 92 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/cedarsoftware/util/Converter.java b/src/main/java/com/cedarsoftware/util/Converter.java index 8c6a6f27..1dd8a2f5 100644 --- a/src/main/java/com/cedarsoftware/util/Converter.java +++ b/src/main/java/com/cedarsoftware/util/Converter.java @@ -454,27 +454,33 @@ public static AtomicBoolean convertToAtomicBoolean(Object fromInstance) } /** + * No longer needed - use convert(localDate, long.class) * @param localDate A Java LocalDate * @return a long representing the localDate as epoch milliseconds (since 1970 Jan 1 at midnight) */ + @Deprecated public static long localDateToMillis(LocalDate localDate) { return instance.convert(localDate, long.class); } /** + * No longer needed - use convert(localDateTime, long.class) * @param localDateTime A Java LocalDateTime * @return a long representing the localDateTime as epoch milliseconds (since 1970 Jan 1 at midnight) */ + @Deprecated public static long localDateTimeToMillis(LocalDateTime localDateTime) { return instance.convert(localDateTime, long.class); } /** + * No longer needed - use convert(ZonedDateTime, long.class) * @param zonedDateTime A Java ZonedDateTime * @return a long representing the ZonedDateTime as epoch milliseconds (since 1970 Jan 1 at midnight) */ + @Deprecated public static long zonedDateTimeToMillis(ZonedDateTime zonedDateTime) { return instance.convert(zonedDateTime, long.class); diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index 99129939..36364d53 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -69,6 +69,7 @@ */ public final class Converter { + static final Convert UNSUPPORTED = Converter::unsupported; static final String VALUE = "_v"; private final Map, Class>, Convert> factory; @@ -112,7 +113,6 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(Double.class, Byte.class), NumberConversions::toByte); DEFAULT_FACTORY.put(pair(Boolean.class, Byte.class), BooleanConversions::toByte); DEFAULT_FACTORY.put(pair(Character.class, Byte.class), CharacterConversions::toByte); - DEFAULT_FACTORY.put(pair(Calendar.class, Byte.class), NumberConversions::toByte); DEFAULT_FACTORY.put(pair(AtomicBoolean.class, Byte.class), AtomicBooleanConversions::toByte); DEFAULT_FACTORY.put(pair(AtomicInteger.class, Byte.class), NumberConversions::toByte); DEFAULT_FACTORY.put(pair(AtomicLong.class, Byte.class), NumberConversions::toByte); @@ -267,7 +267,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(String.class, Boolean.class), StringConversions::toBoolean); DEFAULT_FACTORY.put(pair(Year.class, Boolean.class), YearConversions::toBoolean); - // Character/chat conversions supported + // Character/char conversions supported DEFAULT_FACTORY.put(pair(Void.class, char.class), VoidConversions::toChar); DEFAULT_FACTORY.put(pair(Void.class, Character.class), VoidConversions::toNull); DEFAULT_FACTORY.put(pair(Byte.class, Character.class), NumberConversions::toCharacter); @@ -419,10 +419,14 @@ private static void buildFactoryConversions() { // Date conversions supported DEFAULT_FACTORY.put(pair(Void.class, Date.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, Date.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, Date.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, Date.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, Date.class), NumberConversions::toDate); DEFAULT_FACTORY.put(pair(Double.class, Date.class), NumberConversions::toDate); DEFAULT_FACTORY.put(pair(BigInteger.class, Date.class), NumberConversions::toDate); DEFAULT_FACTORY.put(pair(BigDecimal.class, Date.class), NumberConversions::toDate); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, Date.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, Date.class), NumberConversions::toDate); DEFAULT_FACTORY.put(pair(Date.class, Date.class), DateConversions::toDate); DEFAULT_FACTORY.put(pair(java.sql.Date.class, Date.class), DateConversions::toDate); @@ -439,10 +443,14 @@ private static void buildFactoryConversions() { // java.sql.Date conversion supported DEFAULT_FACTORY.put(pair(Void.class, java.sql.Date.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, java.sql.Date.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, java.sql.Date.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, java.sql.Date.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, java.sql.Date.class), NumberConversions::toSqlDate); DEFAULT_FACTORY.put(pair(Double.class, java.sql.Date.class), NumberConversions::toSqlDate); DEFAULT_FACTORY.put(pair(BigInteger.class, java.sql.Date.class), NumberConversions::toSqlDate); DEFAULT_FACTORY.put(pair(BigDecimal.class, java.sql.Date.class), NumberConversions::toSqlDate); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, java.sql.Date.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, java.sql.Date.class), NumberConversions::toSqlDate); DEFAULT_FACTORY.put(pair(java.sql.Date.class, java.sql.Date.class), DateConversions::toSqlDate); DEFAULT_FACTORY.put(pair(Date.class, java.sql.Date.class), DateConversions::toSqlDate); @@ -459,10 +467,14 @@ private static void buildFactoryConversions() { // Timestamp conversions supported DEFAULT_FACTORY.put(pair(Void.class, Timestamp.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, Timestamp.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, Timestamp.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, Timestamp.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, Timestamp.class), NumberConversions::toTimestamp); DEFAULT_FACTORY.put(pair(Double.class, Timestamp.class), NumberConversions::toTimestamp); DEFAULT_FACTORY.put(pair(BigInteger.class, Timestamp.class), NumberConversions::toTimestamp); DEFAULT_FACTORY.put(pair(BigDecimal.class, Timestamp.class), NumberConversions::toTimestamp); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, Timestamp.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, Timestamp.class), NumberConversions::toTimestamp); DEFAULT_FACTORY.put(pair(Timestamp.class, Timestamp.class), DateConversions::toTimestamp); DEFAULT_FACTORY.put(pair(java.sql.Date.class, Timestamp.class), DateConversions::toTimestamp); @@ -479,10 +491,14 @@ private static void buildFactoryConversions() { // Calendar conversions supported DEFAULT_FACTORY.put(pair(Void.class, Calendar.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, Calendar.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, Calendar.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, Calendar.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, Calendar.class), NumberConversions::toCalendar); DEFAULT_FACTORY.put(pair(Double.class, Calendar.class), NumberConversions::toCalendar); DEFAULT_FACTORY.put(pair(BigInteger.class, Calendar.class), NumberConversions::toCalendar); DEFAULT_FACTORY.put(pair(BigDecimal.class, Calendar.class), NumberConversions::toCalendar); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, Calendar.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, Calendar.class), NumberConversions::toCalendar); DEFAULT_FACTORY.put(pair(Date.class, Calendar.class), DateConversions::toCalendar); DEFAULT_FACTORY.put(pair(java.sql.Date.class, Calendar.class), DateConversions::toCalendar); @@ -499,10 +515,14 @@ private static void buildFactoryConversions() { // LocalDate conversions supported DEFAULT_FACTORY.put(pair(Void.class, LocalDate.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, LocalDate.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, LocalDate.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, LocalDate.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, LocalDate.class), NumberConversions::toLocalDate); DEFAULT_FACTORY.put(pair(Double.class, LocalDate.class), NumberConversions::toLocalDate); DEFAULT_FACTORY.put(pair(BigInteger.class, LocalDate.class), NumberConversions::toLocalDate); DEFAULT_FACTORY.put(pair(BigDecimal.class, LocalDate.class), NumberConversions::toLocalDate); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, LocalDate.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, LocalDate.class), NumberConversions::toLocalDate); DEFAULT_FACTORY.put(pair(java.sql.Date.class, LocalDate.class), DateConversions::toLocalDate); DEFAULT_FACTORY.put(pair(Timestamp.class, LocalDate.class), DateConversions::toLocalDate); @@ -519,10 +539,14 @@ private static void buildFactoryConversions() { // LocalDateTime conversions supported DEFAULT_FACTORY.put(pair(Void.class, LocalDateTime.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, LocalDateTime.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, LocalDateTime.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, LocalDateTime.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, LocalDateTime.class), NumberConversions::toLocalDateTime); DEFAULT_FACTORY.put(pair(Double.class, LocalDateTime.class), NumberConversions::toLocalDateTime); DEFAULT_FACTORY.put(pair(BigInteger.class, LocalDateTime.class), NumberConversions::toLocalDateTime); DEFAULT_FACTORY.put(pair(BigDecimal.class, LocalDateTime.class), NumberConversions::toLocalDateTime); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, LocalDateTime.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, LocalDateTime.class), NumberConversions::toLocalDateTime); DEFAULT_FACTORY.put(pair(java.sql.Date.class, LocalDateTime.class), DateConversions::toLocalDateTime); DEFAULT_FACTORY.put(pair(Timestamp.class, LocalDateTime.class), DateConversions::toLocalDateTime); @@ -539,10 +563,14 @@ private static void buildFactoryConversions() { // LocalTime conversions supported DEFAULT_FACTORY.put(pair(Void.class, LocalTime.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, LocalTime.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, LocalTime.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, LocalTime.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, LocalTime.class), NumberConversions::toLocalTime); DEFAULT_FACTORY.put(pair(Double.class, LocalTime.class), NumberConversions::toLocalTime); DEFAULT_FACTORY.put(pair(BigInteger.class, LocalTime.class), NumberConversions::toLocalTime); DEFAULT_FACTORY.put(pair(BigDecimal.class, LocalTime.class), NumberConversions::toLocalDateTime); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, LocalTime.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, LocalTime.class), NumberConversions::toLocalTime); DEFAULT_FACTORY.put(pair(java.sql.Date.class, LocalTime.class), DateConversions::toLocalTime); DEFAULT_FACTORY.put(pair(Timestamp.class, LocalTime.class), DateConversions::toLocalTime); @@ -560,10 +588,14 @@ private static void buildFactoryConversions() { // ZonedDateTime conversions supported DEFAULT_FACTORY.put(pair(Void.class, ZonedDateTime.class), VoidConversions::toNull); + DEFAULT_FACTORY.put(pair(Byte.class, ZonedDateTime.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, ZonedDateTime.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, ZonedDateTime.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, ZonedDateTime.class), NumberConversions::toZonedDateTime); DEFAULT_FACTORY.put(pair(Double.class, ZonedDateTime.class), NumberConversions::toZonedDateTime); DEFAULT_FACTORY.put(pair(BigInteger.class, ZonedDateTime.class), NumberConversions::toZonedDateTime); DEFAULT_FACTORY.put(pair(BigDecimal.class, ZonedDateTime.class), NumberConversions::toZonedDateTime); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, ZonedDateTime.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, ZonedDateTime.class), NumberConversions::toZonedDateTime); DEFAULT_FACTORY.put(pair(java.sql.Date.class, ZonedDateTime.class), DateConversions::toZonedDateTime); DEFAULT_FACTORY.put(pair(Timestamp.class, ZonedDateTime.class), DateConversions::toZonedDateTime); @@ -657,10 +689,14 @@ private static void buildFactoryConversions() { // Instant conversions supported DEFAULT_FACTORY.put(pair(Void.class, Instant.class), VoidConversions::toNull); DEFAULT_FACTORY.put(pair(Instant.class, Instant.class), Converter::identity); + DEFAULT_FACTORY.put(pair(Byte.class, Instant.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Short.class, Instant.class), UNSUPPORTED); + DEFAULT_FACTORY.put(pair(Integer.class, Instant.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Long.class, Instant.class), NumberConversions::toInstant); DEFAULT_FACTORY.put(pair(Double.class, Instant.class), NumberConversions::toInstant); DEFAULT_FACTORY.put(pair(BigInteger.class, Instant.class), NumberConversions::toInstant); DEFAULT_FACTORY.put(pair(BigDecimal.class, Instant.class), NumberConversions::toInstant); + DEFAULT_FACTORY.put(pair(AtomicInteger.class, Instant.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(AtomicLong.class, Instant.class), NumberConversions::toInstant); DEFAULT_FACTORY.put(pair(java.sql.Date.class, Instant.class), DateConversions::toInstant); DEFAULT_FACTORY.put(pair(Timestamp.class, Instant.class), DateConversions::toInstant); @@ -762,6 +798,7 @@ private static void buildFactoryConversions() { // toYear DEFAULT_FACTORY.put(pair(Void.class, Year.class), VoidConversions::toNull); DEFAULT_FACTORY.put(pair(Year.class, Year.class), Converter::identity); + DEFAULT_FACTORY.put(pair(Byte.class, Year.class), UNSUPPORTED); DEFAULT_FACTORY.put(pair(Number.class, Year.class), NumberConversions::toYear); DEFAULT_FACTORY.put(pair(String.class, Year.class), StringConversions::toYear); DEFAULT_FACTORY.put(pair(Map.class, Year.class), MapConversions::toYear); @@ -883,13 +920,13 @@ public T convert(Object from, Class toType, ConverterOptions options) { // Direct Mapping Convert converter = factory.get(pair(sourceType, toType)); - if (converter != null) { + if (converter != null && converter != UNSUPPORTED) { return (T) converter.convert(from, this, options); } // Try inheritance converter = getInheritedConverter(sourceType, toType); - if (converter != null) { + if (converter != null && converter != UNSUPPORTED) { // Fast lookup next time. if (!isDirectConversionSupportedFor(sourceType, toType)) { addConversion(sourceType, toType, converter); @@ -1016,7 +1053,8 @@ static private String name(Object from) { boolean isDirectConversionSupportedFor(Class source, Class target) { source = toPrimitiveWrapperClass(source); target = toPrimitiveWrapperClass(target); - return factory.containsKey(pair(source, target)); + Convert method = factory.get(pair(source, target)); + return method != null && method != UNSUPPORTED; } /** @@ -1029,10 +1067,13 @@ boolean isDirectConversionSupportedFor(Class source, Class target) { public boolean isConversionSupportedFor(Class source, Class target) { source = toPrimitiveWrapperClass(source); target = toPrimitiveWrapperClass(target); - if (factory.containsKey(pair(source, target))) { + Convert method = factory.get(pair(source, target)); + if (method != null && method != UNSUPPORTED) { return true; } - return getInheritedConverter(source, target) != null; + + method = getInheritedConverter(source, target); + return method != null && method != UNSUPPORTED; } /** @@ -1042,8 +1083,11 @@ public boolean isConversionSupportedFor(Class source, Class target) { public Map, Set>> allSupportedConversions() { Map, Set>> toFrom = new TreeMap<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName())); - for (Map.Entry, Class> pairs : factory.keySet()) { - toFrom.computeIfAbsent(pairs.getKey(), k -> new TreeSet<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName()))).add(pairs.getValue()); + for (Map.Entry, Class>, Convert> entry : factory.entrySet()) { + if (entry.getValue() != UNSUPPORTED) { + Map.Entry, Class> pair = entry.getKey(); + toFrom.computeIfAbsent(pair.getKey(), k -> new TreeSet<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName()))).add(pair.getValue()); + } } return toFrom; } @@ -1055,8 +1099,11 @@ public Map, Set>> allSupportedConversions() { public Map> getSupportedConversions() { Map> toFrom = new TreeMap<>(String::compareToIgnoreCase); - for (Map.Entry, Class> pairs : factory.keySet()) { - toFrom.computeIfAbsent(getShortName(pairs.getKey()), k -> new TreeSet<>(String::compareToIgnoreCase)).add(getShortName(pairs.getValue())); + for (Map.Entry, Class>, Convert> entry : factory.entrySet()) { + if (entry.getValue() != UNSUPPORTED) { + Map.Entry, Class> pair = entry.getKey(); + toFrom.computeIfAbsent(getShortName(pair.getKey()), k -> new TreeSet<>(String::compareToIgnoreCase)).add(getShortName(pair.getValue())); + } } return toFrom; } @@ -1092,7 +1139,11 @@ private static Class toPrimitiveWrapperClass(Class primitiveClass) { return c; } - private static T identity(T one, Converter converter, ConverterOptions options) { - return one; + private static T identity(T from, Converter converter, ConverterOptions options) { + return from; + } + + private static T unsupported(T from, Converter converter, ConverterOptions options) { + return (T) UNSUPPORTED; } } diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java index 5c4477fc..10d0361e 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java @@ -2210,17 +2210,9 @@ void conversionsWithPrecisionLoss_toAtomicLong(Object value, AtomicLong expected private static Stream extremeDateParams() { return Stream.of( - Arguments.of((short)75, new Date(75)), - Arguments.of(Byte.MIN_VALUE, new Date(Byte.MIN_VALUE)), - Arguments.of(Byte.MAX_VALUE, new Date(Byte.MAX_VALUE)), - Arguments.of(Short.MIN_VALUE, new Date(Short.MIN_VALUE)), - Arguments.of(Short.MAX_VALUE, new Date(Short.MAX_VALUE)), - Arguments.of(Integer.MIN_VALUE, new Date(Integer.MIN_VALUE)), - Arguments.of(Integer.MAX_VALUE, new Date(Integer.MAX_VALUE)), Arguments.of(Long.MIN_VALUE,new Date(Long.MIN_VALUE)), Arguments.of(Long.MAX_VALUE, new Date(Long.MAX_VALUE)), - Arguments.of(127.0d, new Date(127)), - Arguments.of( new AtomicInteger(25), new Date(25)) + Arguments.of(127.0d, new Date(127)) ); } @@ -3904,13 +3896,13 @@ void testAllSupportedConversions() @Test void testIsConversionSupport() { - assert this.converter.isConversionSupportedFor(int.class, LocalDate.class); - assert this.converter.isConversionSupportedFor(Integer.class, LocalDate.class); + assert !this.converter.isConversionSupportedFor(int.class, LocalDate.class); + assert !this.converter.isConversionSupportedFor(Integer.class, LocalDate.class); assert !this.converter.isDirectConversionSupportedFor(byte.class, LocalDate.class); - assert this.converter.isConversionSupportedFor(byte.class, LocalDate.class); // byte is upgraded to Byte, which is found as Number. + assert !this.converter.isConversionSupportedFor(byte.class, LocalDate.class); - assert this.converter.isConversionSupportedFor(Byte.class, LocalDate.class); // Number is supported + assert !this.converter.isConversionSupportedFor(Byte.class, LocalDate.class); assert !this.converter.isDirectConversionSupportedFor(Byte.class, LocalDate.class); assert !this.converter.isConversionSupportedFor(LocalDate.class, byte.class); assert !this.converter.isConversionSupportedFor(LocalDate.class, Byte.class); @@ -4332,6 +4324,23 @@ void testStringToCharArray(String source, Charset charSet, char[] expected) { char[] actual = this.converter.convert(source, char[].class, createCharsetOptions(charSet)); assertThat(actual).isEqualTo(expected); } + + @Test + void testKnownUnsupportedConversions() + { + System.out.println(converter.getSupportedConversions()); + assertThatThrownBy(() -> converter.convert((byte)50, Date.class)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unsupported conversion"); + + assertThatThrownBy(() -> converter.convert((short)300, Date.class)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unsupported conversion"); + + assertThatThrownBy(() -> converter.convert(100000, Date.class)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unsupported conversion"); + } private ConverterOptions createCharsetOptions(final Charset charset) {