From 3ce03dd2cffac9a2a49d9d0bbb28709fb5e7ebe9 Mon Sep 17 00:00:00 2001 From: Ken Partlow Date: Tue, 13 Feb 2024 00:26:38 -0500 Subject: [PATCH] Allowed converter overrides from outside --- .../cedarsoftware/util/ClassUtilities.java | 27 +++++++++++ .../cedarsoftware/util/convert/Converter.java | 48 +++++-------------- .../util/convert/ConverterOptions.java | 8 ++++ .../util/convert/DefaultConverterOptions.java | 10 ++-- .../util/convert/ConverterEverythingTest.java | 6 ++- 5 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/cedarsoftware/util/ClassUtilities.java b/src/main/java/com/cedarsoftware/util/ClassUtilities.java index 185f9342..e27cc4c3 100644 --- a/src/main/java/com/cedarsoftware/util/ClassUtilities.java +++ b/src/main/java/com/cedarsoftware/util/ClassUtilities.java @@ -40,6 +40,7 @@ public class ClassUtilities { private static final Set> prims = new HashSet<>(); + private static final Map, Class> primitiveToWrapper = new HashMap<>(20, .8f); private static final Map> nameToClass = new HashMap<>(); static @@ -65,6 +66,16 @@ public class ClassUtilities nameToClass.put("date", Date.class); nameToClass.put("class", Class.class); + primitiveToWrapper.put(int.class, Integer.class); + primitiveToWrapper.put(long.class, Long.class); + primitiveToWrapper.put(double.class, Double.class); + primitiveToWrapper.put(float.class, Float.class); + primitiveToWrapper.put(boolean.class, Boolean.class); + primitiveToWrapper.put(char.class, Character.class); + primitiveToWrapper.put(byte.class, Byte.class); + primitiveToWrapper.put(short.class, Short.class); + primitiveToWrapper.put(void.class, Void.class); + } /** @@ -307,4 +318,20 @@ public static boolean areAllConstructorsPrivate(Class c) { return true; } + + public static Class toPrimitiveWrapperClass(Class primitiveClass) { + if (!primitiveClass.isPrimitive()) { + return primitiveClass; + } + + Class c = primitiveToWrapper.get(primitiveClass); + + if (c == null) { + throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class"); + } + + return c; + } + + } diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index c64e00cf..a691c174 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -23,7 +23,6 @@ import java.util.AbstractMap; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -35,6 +34,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import com.cedarsoftware.util.ClassUtilities; + /** * Instance conversion utility. Convert from primitive to other primitives, plus support for Number, Date, * TimeStamp, SQL Date, LocalDate, LocalDateTime, ZonedDateTime, Calendar, Big*, Atomic*, Class, UUID, @@ -78,7 +79,6 @@ public final class Converter { private final ConverterOptions options; private static final Map, Set> cacheParentTypes = new ConcurrentHashMap<>(); - private static final Map, Class> primitiveToWrapper = new HashMap<>(20, .8f); private static final Map, Class>, Convert> CONVERSION_DB = new ConcurrentHashMap<>(500, .8f); // Create a Map.Entry (pair) of source class to target class. @@ -87,7 +87,6 @@ static Map.Entry, Class> pair(Class source, Class target) { } static { - buildPrimitiveWrappers(); buildFactoryConversions(); } @@ -95,18 +94,6 @@ public ConverterOptions getOptions() { return options; } - private static void buildPrimitiveWrappers() { - primitiveToWrapper.put(int.class, Integer.class); - primitiveToWrapper.put(long.class, Long.class); - primitiveToWrapper.put(double.class, Double.class); - primitiveToWrapper.put(float.class, Float.class); - primitiveToWrapper.put(boolean.class, Boolean.class); - primitiveToWrapper.put(char.class, Character.class); - primitiveToWrapper.put(byte.class, Byte.class); - primitiveToWrapper.put(short.class, Short.class); - primitiveToWrapper.put(void.class, Void.class); - } - private static void buildFactoryConversions() { // toByte CONVERSION_DB.put(pair(Void.class, byte.class), NumberConversions::toByteZero); @@ -859,6 +846,7 @@ private static void buildFactoryConversions() { public Converter(ConverterOptions options) { this.options = options; this.factory = new ConcurrentHashMap<>(CONVERSION_DB); + this.factory.putAll(this.options.getConverterOverrides()); } /** @@ -901,7 +889,7 @@ public T convert(Object from, Class toType) { // Promote primitive to primitive wrapper, so we don't have to define so many duplicates in the factory map. sourceType = from.getClass(); if (toType.isPrimitive()) { - toType = (Class) toPrimitiveWrapperClass(toType); + toType = (Class) ClassUtilities.toPrimitiveWrapperClass(toType); } } @@ -1038,8 +1026,8 @@ static private String name(Object from) { * @return boolean true if the Converter converts from the source type to the destination type, false otherwise. */ boolean isDirectConversionSupportedFor(Class source, Class target) { - source = toPrimitiveWrapperClass(source); - target = toPrimitiveWrapperClass(target); + source = ClassUtilities.toPrimitiveWrapperClass(source); + target = ClassUtilities.toPrimitiveWrapperClass(target); Convert method = factory.get(pair(source, target)); return method != null && method != UNSUPPORTED; } @@ -1052,8 +1040,8 @@ boolean isDirectConversionSupportedFor(Class source, Class target) { * @return boolean true if the Converter converts from the source type to the destination type, false otherwise. */ public boolean isConversionSupportedFor(Class source, Class target) { - source = toPrimitiveWrapperClass(source); - target = toPrimitiveWrapperClass(target); + source = ClassUtilities.toPrimitiveWrapperClass(source); + target = ClassUtilities.toPrimitiveWrapperClass(target); Convert method = factory.get(pair(source, target)); if (method != null && method != UNSUPPORTED) { return true; @@ -1104,28 +1092,14 @@ public Map> getSupportedConversions() { * @return prior conversion function if one existed. */ public Convert addConversion(Class source, Class target, Convert conversionFunction) { - source = toPrimitiveWrapperClass(source); - target = toPrimitiveWrapperClass(target); + source = ClassUtilities.toPrimitiveWrapperClass(source); + target = ClassUtilities.toPrimitiveWrapperClass(target); return factory.put(pair(source, target), conversionFunction); } - + /** * Given a primitive class, return the Wrapper class equivalent. */ - static Class toPrimitiveWrapperClass(Class primitiveClass) { - if (!primitiveClass.isPrimitive()) { - return primitiveClass; - } - - Class c = primitiveToWrapper.get(primitiveClass); - - if (c == null) { - throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class"); - } - - return c; - } - private static T identity(T from, Converter converter) { return from; } diff --git a/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java b/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java index 36e19e3a..a319cd4f 100644 --- a/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java +++ b/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java @@ -5,7 +5,9 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.TimeZone; /** @@ -68,4 +70,10 @@ public interface ConverterOptions { * @return the Character representing false */ default Character falseChar() { return CommonValues.CHARACTER_ZERO; } + + /** + * Overrides for converter conversions.. + * @return The Map of overrides. + */ + default Map, Class>, Convert> getConverterOverrides() { return new HashMap<>(); } } diff --git a/src/main/java/com/cedarsoftware/util/convert/DefaultConverterOptions.java b/src/main/java/com/cedarsoftware/util/convert/DefaultConverterOptions.java index 2e9d23ec..5f44319f 100644 --- a/src/main/java/com/cedarsoftware/util/convert/DefaultConverterOptions.java +++ b/src/main/java/com/cedarsoftware/util/convert/DefaultConverterOptions.java @@ -1,9 +1,5 @@ package com.cedarsoftware.util.convert; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.ZoneId; -import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -28,8 +24,11 @@ public class DefaultConverterOptions implements ConverterOptions { private final Map customOptions; + private final Map, Class>, Convert> converterOverrides; + public DefaultConverterOptions() { this.customOptions = new ConcurrentHashMap<>(); + this.converterOverrides = new ConcurrentHashMap<>(); } @SuppressWarnings("unchecked") @@ -37,4 +36,7 @@ public DefaultConverterOptions() { public T getCustomOption(String name) { return (T) this.customOptions.get(name); } + + @Override + public Map, Class>, Convert> getConverterOverrides() { return this.converterOverrides; } } diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index f858bfff..9c9d4b8e 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -43,6 +43,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import com.cedarsoftware.util.ClassUtilities; + import static com.cedarsoftware.util.MapUtilities.mapOf; import static com.cedarsoftware.util.convert.Converter.getShortName; import static com.cedarsoftware.util.convert.Converter.pair; @@ -1676,9 +1678,9 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source, if (source == null) { assertEquals(sourceClass, Void.class, "On the source-side of test input, null can only appear in the Void.class data"); } else { - assertTrue(Converter.toPrimitiveWrapperClass(sourceClass).isInstance(source), "source type mismatch"); + assertTrue(ClassUtilities.toPrimitiveWrapperClass(sourceClass).isInstance(source), "source type mismatch"); } - assertTrue(target == null || target instanceof Throwable || Converter.toPrimitiveWrapperClass(targetClass).isInstance(target), "target type mismatch"); + assertTrue(target == null || target instanceof Throwable || ClassUtilities.toPrimitiveWrapperClass(targetClass).isInstance(target), "target type mismatch"); // if the source/target are the same Class, then ensure identity lambda is used. if (sourceClass.equals(targetClass)) {