Skip to content

Commit

Permalink
- Performance improvement on Converter - user added conversions are k…
Browse files Browse the repository at this point in the history
…ept 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.
  • Loading branch information
jdereg committed Apr 17, 2024
1 parent 64a9e4c commit b662db3
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 23 deletions.
68 changes: 49 additions & 19 deletions src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map.Entry<Class<?>, Class<?>>, Convert<?>> factory;
private final ConverterOptions options;

private static final Map<Class<?>, Set<ClassLevel>> cacheParentTypes = new ConcurrentHashMap<>();
private static final Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> CONVERSION_DB = new ConcurrentHashMap<>(500, .8f);
private static final Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> CONVERSION_DB = new ConcurrentHashMap<>(860, .8f); // =~680/0.8
private final Map<Map.Entry<Class<?>, 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<?>, Class<?>> pair(Class<?> source, Class<?> target) {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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());
}

/**
Expand Down Expand Up @@ -929,8 +927,14 @@ public <T> T convert(Object from, Class<T> 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);
}
Expand Down Expand Up @@ -959,15 +963,24 @@ private <T> Convert<?> getInheritedConverter(Class<?> sourceType, Class<T> 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;
}
}
Expand All @@ -977,7 +990,6 @@ private <T> Convert<?> getInheritedConverter(Class<?> sourceType, Class<T> toTyp
}
}

Convert<?> converter = factory.get(pair(sourceClass, targetClass));
return converter;
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand All @@ -1093,14 +1115,18 @@ public boolean isConversionSupportedFor(Class<?> source, Class<?> target) {
*/
public Map<Class<?>, Set<Class<?>>> allSupportedConversions() {
Map<Class<?>, Set<Class<?>>> toFrom = new TreeMap<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName()));
addSupportedConversion(CONVERSION_DB, toFrom);
addSupportedConversion(USER_DB, toFrom);
return toFrom;
}

for (Map.Entry<Map.Entry<Class<?>, Class<?>>, Convert<?>> entry : factory.entrySet()) {
private static void addSupportedConversion(Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> db, Map<Class<?>, Set<Class<?>>> toFrom) {
for (Map.Entry<Map.Entry<Class<?>, Class<?>>, Convert<?>> entry : db.entrySet()) {
if (entry.getValue() != UNSUPPORTED) {
Map.Entry<Class<?>, Class<?>> pair = entry.getKey();
toFrom.computeIfAbsent(pair.getKey(), k -> new TreeSet<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName()))).add(pair.getValue());
}
}
return toFrom;
}

/**
Expand All @@ -1109,14 +1135,18 @@ public Map<Class<?>, Set<Class<?>>> allSupportedConversions() {
*/
public Map<String, Set<String>> getSupportedConversions() {
Map<String, Set<String>> toFrom = new TreeMap<>(String::compareToIgnoreCase);
addSupportedConversionName(CONVERSION_DB, toFrom);
addSupportedConversionName(USER_DB, toFrom);
return toFrom;
}

for (Map.Entry<Map.Entry<Class<?>, Class<?>>, Convert<?>> entry : factory.entrySet()) {
private static void addSupportedConversionName(Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> db, Map<String, Set<String>> toFrom) {
for (Map.Entry<Map.Entry<Class<?>, Class<?>>, Convert<?>> entry : db.entrySet()) {
if (entry.getValue() != UNSUPPORTED) {
Map.Entry<Class<?>, Class<?>> pair = entry.getKey();
toFrom.computeIfAbsent(getShortName(pair.getKey()), k -> new TreeSet<>(String::compareToIgnoreCase)).add(getShortName(pair.getValue()));
}
}
return toFrom;
}

/**
Expand All @@ -1130,7 +1160,7 @@ public Map<String, Set<String>> 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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map.Entry<Class<?>, Class<?>>, Convert<?>> getConverterOverrides() { return new HashMap<>(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")},
});
}

/**
Expand Down Expand Up @@ -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")},
});
}

/**
Expand All @@ -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) {
Expand Down

0 comments on commit b662db3

Please sign in to comment.