From b662db34eee9303775a1a799136ee1d176948650 Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Tue, 16 Apr 2024 22:45:05 -0400 Subject: [PATCH] - Performance improvement on Converter - user added conversions are kept separate from the factory conversions. Both DB's (maps) are checked at runtime. For each instance of Converter created, no more big delay while it copied the entire Factory conversion DB to the instance DB. The factory DB is shared by all instances and is unchangeable. --- .../cedarsoftware/util/convert/Converter.java | 68 +++++++++++++------ .../util/convert/ConverterOptions.java | 2 +- .../util/convert/MapConversions.java | 8 +++ .../util/convert/ConverterEverythingTest.java | 14 +++- 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index 41a1f782..6fdd6930 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -75,16 +75,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - public final class Converter { private static final Convert UNSUPPORTED = Converter::unsupported; static final String VALUE = "_v"; - - private final Map, Class>, Convert> factory; - private final ConverterOptions options; - private static final Map, Set> cacheParentTypes = new ConcurrentHashMap<>(); - private static final Map, Class>, Convert> CONVERSION_DB = new ConcurrentHashMap<>(500, .8f); + private static final Map, Class>, Convert> CONVERSION_DB = new ConcurrentHashMap<>(860, .8f); // =~680/0.8 + private final Map, Class>, Convert> USER_DB = new ConcurrentHashMap<>(); + private final ConverterOptions options; // Create a Map.Entry (pair) of source class to target class. static Map.Entry, Class> pair(Class source, Class target) { @@ -758,6 +755,7 @@ private static void buildFactoryConversions() { CONVERSION_DB.put(pair(Character[].class, StringBuffer.class), CharacterArrayConversions::toStringBuffer); CONVERSION_DB.put(pair(char[].class, StringBuffer.class), CharArrayConversions::toStringBuffer); CONVERSION_DB.put(pair(byte[].class, StringBuffer.class), ByteArrayConversions::toStringBuffer); + CONVERSION_DB.put(pair(Map.class, StringBuffer.class), MapConversions::toStringBuffer); // toStringBuilder CONVERSION_DB.put(pair(Void.class, StringBuilder.class), VoidConversions::toNull); @@ -769,6 +767,7 @@ private static void buildFactoryConversions() { CONVERSION_DB.put(pair(Character[].class, StringBuilder.class), CharacterArrayConversions::toStringBuilder); CONVERSION_DB.put(pair(char[].class, StringBuilder.class), CharArrayConversions::toStringBuilder); CONVERSION_DB.put(pair(byte[].class, StringBuilder.class), ByteArrayConversions::toStringBuilder); + CONVERSION_DB.put(pair(Map.class, StringBuilder.class), MapConversions::toStringBuilder); // toByteArray CONVERSION_DB.put(pair(Void.class, byte[].class), VoidConversions::toNull); @@ -881,8 +880,7 @@ private static void buildFactoryConversions() { public Converter(ConverterOptions options) { this.options = options; - this.factory = new ConcurrentHashMap<>(CONVERSION_DB); - this.factory.putAll(this.options.getConverterOverrides()); + USER_DB.putAll(this.options.getConverterOverrides()); } /** @@ -929,8 +927,14 @@ public T convert(Object from, Class toType) { } } - // Direct Mapping - Convert converter = factory.get(pair(sourceType, toType)); + // Check user added conversions (allows overriding factory conversions) + Convert converter = USER_DB.get(pair(sourceType, toType)); + if (converter != null && converter != UNSUPPORTED) { + return (T) converter.convert(from, this); + } + + // Check factory conversion database + converter = CONVERSION_DB.get(pair(sourceType, toType)); if (converter != null && converter != UNSUPPORTED) { return (T) converter.convert(from, this); } @@ -959,15 +963,24 @@ private Convert getInheritedConverter(Class sourceType, Class toTyp Class sourceClass = sourceType; Class targetClass = toType; + Convert converter = null; for (ClassLevel toClassLevel : targetTypes) { sourceClass = null; targetClass = null; for (ClassLevel fromClassLevel : sourceTypes) { - if (factory.containsKey(pair(fromClassLevel.clazz, toClassLevel.clazz))) { + // Check USER_DB first, to ensure that user added conversions override factory conversions. + if (USER_DB.containsKey(pair(fromClassLevel.clazz, toClassLevel.clazz))) { sourceClass = fromClassLevel.clazz; targetClass = toClassLevel.clazz; + converter = USER_DB.get(pair(sourceClass, targetClass)); + break; + } + if (CONVERSION_DB.containsKey(pair(fromClassLevel.clazz, toClassLevel.clazz))) { + sourceClass = fromClassLevel.clazz; + targetClass = toClassLevel.clazz; + converter = CONVERSION_DB.get(pair(sourceClass, targetClass)); break; } } @@ -977,7 +990,6 @@ private Convert getInheritedConverter(Class sourceType, Class toTyp } } - Convert converter = factory.get(pair(sourceClass, targetClass)); return converter; } @@ -1064,7 +1076,12 @@ static private String name(Object from) { boolean isDirectConversionSupportedFor(Class source, Class target) { source = ClassUtilities.toPrimitiveWrapperClass(source); target = ClassUtilities.toPrimitiveWrapperClass(target); - Convert method = factory.get(pair(source, target)); + Convert method = USER_DB.get(pair(source, target)); + if (method != null && method != UNSUPPORTED) { + return true; + } + + method = CONVERSION_DB.get(pair(source, target)); return method != null && method != UNSUPPORTED; } @@ -1078,7 +1095,12 @@ boolean isDirectConversionSupportedFor(Class source, Class target) { public boolean isConversionSupportedFor(Class source, Class target) { source = ClassUtilities.toPrimitiveWrapperClass(source); target = ClassUtilities.toPrimitiveWrapperClass(target); - Convert method = factory.get(pair(source, target)); + Convert method = USER_DB.get(pair(source, target)); + if (method != null && method != UNSUPPORTED) { + return true; + } + + method = CONVERSION_DB.get(pair(source, target)); if (method != null && method != UNSUPPORTED) { return true; } @@ -1093,14 +1115,18 @@ public boolean isConversionSupportedFor(Class source, Class target) { */ public Map, Set>> allSupportedConversions() { Map, Set>> toFrom = new TreeMap<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName())); + addSupportedConversion(CONVERSION_DB, toFrom); + addSupportedConversion(USER_DB, toFrom); + return toFrom; + } - for (Map.Entry, Class>, Convert> entry : factory.entrySet()) { + private static void addSupportedConversion(Map, Class>, Convert> db, Map, Set>> toFrom) { + for (Map.Entry, Class>, Convert> entry : db.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; } /** @@ -1109,14 +1135,18 @@ public Map, Set>> allSupportedConversions() { */ public Map> getSupportedConversions() { Map> toFrom = new TreeMap<>(String::compareToIgnoreCase); + addSupportedConversionName(CONVERSION_DB, toFrom); + addSupportedConversionName(USER_DB, toFrom); + return toFrom; + } - for (Map.Entry, Class>, Convert> entry : factory.entrySet()) { + private static void addSupportedConversionName(Map, Class>, Convert> db, Map> toFrom) { + for (Map.Entry, Class>, Convert> entry : db.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; } /** @@ -1130,7 +1160,7 @@ public Map> getSupportedConversions() { public Convert addConversion(Class source, Class target, Convert conversionFunction) { source = ClassUtilities.toPrimitiveWrapperClass(source); target = ClassUtilities.toPrimitiveWrapperClass(target); - return factory.put(pair(source, target), conversionFunction); + return USER_DB.put(pair(source, target), conversionFunction); } /** diff --git a/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java b/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java index a319cd4f..86520f53 100644 --- a/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java +++ b/src/main/java/com/cedarsoftware/util/convert/ConverterOptions.java @@ -72,7 +72,7 @@ public interface ConverterOptions { default Character falseChar() { return CommonValues.CHARACTER_ZERO; } /** - * Overrides for converter conversions.. + * 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/MapConversions.java b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java index 6d95fe0b..7fce43c8 100644 --- a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java @@ -153,6 +153,14 @@ static String toString(Object from, Converter converter) { return fromMap(from, converter, String.class); } + static StringBuffer toStringBuffer(Object from, Converter converter) { + return fromMap(from, converter, StringBuffer.class); + } + + static StringBuilder toStringBuilder(Object from, Converter converter) { + return fromMap(from, converter, StringBuilder.class); + } + static Character toCharacter(Object from, Converter converter) { return fromMap(from, converter, char.class); } diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index 8776672f..510884cf 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -794,9 +794,6 @@ private static void loadStringTests() { TEST_DB.put(pair(String.class, String.class), new Object[][]{ {"same", "same"}, }); - TEST_DB.put(pair(String.class, StringBuffer.class), new Object[][]{ - {"same", new StringBuffer("same")}, - }); } /** @@ -3604,6 +3601,13 @@ private static void loadStringBufferTest() { TEST_DB.put(pair(Character[].class, StringBuffer.class), new Object[][]{ {new Character[] { 'H', 'i' }, new StringBuffer("Hi"), true}, }); + TEST_DB.put(pair(String.class, StringBuffer.class), new Object[][]{ + {"same", new StringBuffer("same")}, + }); + TEST_DB.put(pair(Map.class, StringBuffer.class), new Object[][]{ + {mapOf("_v", "alpha"), new StringBuffer("alpha")}, + {mapOf("value", "beta"), new StringBuffer("beta")}, + }); } /** @@ -3625,6 +3629,10 @@ private static void loadStringBuilderTest() { TEST_DB.put(pair(StringBuffer.class, StringBuilder.class), new Object[][]{ {new StringBuffer("Poker"), new StringBuilder("Poker"), true}, }); + TEST_DB.put(pair(Map.class, StringBuilder.class), new Object[][]{ + {mapOf("_v", "alpha"), new StringBuilder("alpha")}, + {mapOf("value", "beta"), new StringBuilder("beta")}, + }); } private static URL toURL(String url) {