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) {