Skip to content

Commit

Permalink
- Inherited classes now follow precise ordering when being accessed w…
Browse files Browse the repository at this point in the history
…hen searching for parent class in vector (source ==> target) mapping.

- Classes are now added dynamically when found via inheritance, so subsequent conversions will get a direect mapping
- Using singleton instance zoneId for static methods on Converter.
  • Loading branch information
jdereg committed Jan 14, 2024
1 parent 5356beb commit 154b091
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 50 deletions.
6 changes: 4 additions & 2 deletions src/main/java/com/cedarsoftware/util/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ public static AtomicBoolean convertToAtomicBoolean(Object fromInstance)
*/
public static long localDateToMillis(LocalDate localDate)
{
return localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
ZoneId zoneId = instance.getOptions().getSourceZoneId();
return localDate.atStartOfDay(zoneId).toInstant().toEpochMilli();
}

/**
Expand All @@ -431,7 +432,8 @@ public static long localDateToMillis(LocalDate localDate)
*/
public static long localDateTimeToMillis(LocalDateTime localDateTime)
{
return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
ZoneId zoneId = instance.getOptions().getSourceZoneId();
return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
}

/**
Expand Down
137 changes: 89 additions & 48 deletions src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cedarsoftware.util.convert;


import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
Expand All @@ -18,7 +19,6 @@
import java.time.format.DateTimeFormatter;
import java.util.AbstractMap;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -79,14 +79,22 @@ public final class Converter {
private static final String VALUE2 = "value";

private final Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> factory;

private final ConverterOptions options;

private static final Map<Class<?>, Set<Class<?>>> cacheParentTypes = new ConcurrentHashMap<>();
private static final Map<Class<?>, Set<ClassLevel>> cacheParentTypes = new ConcurrentHashMap<>();
private static final Map<Class<?>, Class<?>> primitiveToWrapper = new HashMap<>(20, .8f);

private static final Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> DEFAULT_FACTORY = new HashMap<>(500, .8f);

private static final BigDecimal bigDecimalMinByte = BigDecimal.valueOf(Byte.MIN_VALUE);
private static final BigDecimal bigDecimalMaxByte = BigDecimal.valueOf(Byte.MAX_VALUE);
private static final BigDecimal bigDecimalMinShort = BigDecimal.valueOf(Short.MIN_VALUE);
private static final BigDecimal bigDecimalMaxShort = BigDecimal.valueOf(Short.MAX_VALUE);
private static final BigDecimal bigDecimalMinInteger = BigDecimal.valueOf(Integer.MIN_VALUE);
private static final BigDecimal bigDecimalMaxInteger = BigDecimal.valueOf(Integer.MAX_VALUE);
private static final BigDecimal bigDecimalMaxLong = BigDecimal.valueOf(Long.MAX_VALUE);
private static final BigDecimal bigDecimalMinLong = BigDecimal.valueOf(Long.MIN_VALUE);

// Create a Map.Entry (pair) of source class to target class.
private static Map.Entry<Class<?>, Class<?>> pair(Class<?> source, Class<?> target) {
return new AbstractMap.SimpleImmutableEntry<>(source, target);
}
Expand Down Expand Up @@ -1056,6 +1064,11 @@ public Converter(ConverterOptions options) {
this.factory = new ConcurrentHashMap<>(DEFAULT_FACTORY);
}

public ConverterOptions getOptions()
{
return options;
}

/**
* Turn the passed in value to the class indicated. This will allow, for
* example, a String to be passed in and be converted to a Long.
Expand Down Expand Up @@ -1141,6 +1154,10 @@ public <T> T convert(Object fromInstance, Class<T> toType, ConverterOptions opti
// Try inheritance
converter = getInheritedConverter(sourceType, toType);
if (converter != null) {
// Fast lookup next time.
if (!isDirectConversionSupportedFor(sourceType, toType)) {
addConversion(sourceType, toType, converter);
}
return (T) converter.convert(fromInstance, this, options);
}

Expand All @@ -1151,25 +1168,22 @@ public <T> T convert(Object fromInstance, Class<T> toType, ConverterOptions opti
* Expected that source and target classes, if primitive, have already been shifted to primitive wrapper classes.
*/
private <T> Convert<?> getInheritedConverter(Class<?> sourceType, Class<T> toType) {
Set<Class<?>> sourceTypes = new TreeSet<>(getClassComparator());
Set<Class<?>> targetTypes = new TreeSet<>(getClassComparator());

sourceTypes.addAll(getSuperClassesAndInterfaces(sourceType));
sourceTypes.add(sourceType);
targetTypes.addAll(getSuperClassesAndInterfaces(toType));
targetTypes.add(toType);
Set<ClassLevel> sourceTypes = new TreeSet<>(getSuperClassesAndInterfaces(sourceType));
sourceTypes.add(new ClassLevel(sourceType, 0));
Set<ClassLevel> targetTypes = new TreeSet<>(getSuperClassesAndInterfaces(toType));
targetTypes.add(new ClassLevel(toType, 0));

Class<?> sourceClass = sourceType;
Class<?> targetClass = toType;

for (Class<?> toClass : targetTypes) {
for (ClassLevel toClassLevel : targetTypes) {
sourceClass = null;
targetClass = null;

for (Class<?> fromClass : sourceTypes) {
if (factory.containsKey(pair(fromClass, toClass))) {
sourceClass = fromClass;
targetClass = toClass;
for (ClassLevel fromClassLevel : sourceTypes) {
if (factory.containsKey(pair(fromClassLevel.clazz, toClassLevel.clazz))) {
sourceClass = fromClassLevel.clazz;
targetClass = toClassLevel.clazz;
break;
}
}
Expand All @@ -1183,40 +1197,78 @@ private <T> Convert<?> getInheritedConverter(Class<?> sourceType, Class<T> toTyp
return converter;
}

private static Comparator<Class<?>> getClassComparator() {
return (c1, c2) -> {
if (c1.isInterface() == c2.isInterface()) {
// By name
return c1.getName().compareToIgnoreCase(c2.getName());
}
return c1.isInterface() ? 1 : -1;
};
}

private static Set<Class<?>> getSuperClassesAndInterfaces(Class<?> clazz) {

Set<Class<?>> parentTypes = cacheParentTypes.get(clazz);
private static Set<ClassLevel> getSuperClassesAndInterfaces(Class<?> clazz) {
Set<ClassLevel> parentTypes = cacheParentTypes.get(clazz);
if (parentTypes != null) {
return parentTypes;
}
parentTypes = new ConcurrentSkipListSet<>(getClassComparator());
addSuperClassesAndInterfaces(clazz, parentTypes);
parentTypes = new ConcurrentSkipListSet<>();
addSuperClassesAndInterfaces(clazz, parentTypes, 1);
cacheParentTypes.put(clazz, parentTypes);
return parentTypes;
}

private static void addSuperClassesAndInterfaces(Class<?> clazz, Set<Class<?>> result) {
static class ClassLevel implements Comparable {
private final Class<?> clazz;
private final int level;

ClassLevel(Class<?> c, int level) {
clazz = c;
this.level = level;
}

public int hashCode() {
return clazz.hashCode();
}

public boolean equals(Object o)
{
if (!(o instanceof ClassLevel)) {
return false;
}

return clazz.equals(((ClassLevel) o).clazz);
}

public int compareTo(Object o) {
if (!(o instanceof ClassLevel)) {
throw new IllegalArgumentException("Object must be of type ClassLevel");
}
ClassLevel other = (ClassLevel) o;

// Primary sort key: level
int levelComparison = Integer.compare(this.level, other.level);
if (levelComparison != 0) {
return levelComparison;
}

// Secondary sort key: clazz type (class vs interface)
boolean thisIsInterface = this.clazz.isInterface();
boolean otherIsInterface = other.clazz.isInterface();
if (thisIsInterface != otherIsInterface) {
return thisIsInterface ? 1 : -1;
}

// Tertiary sort key: class name
return this.clazz.getName().compareTo(other.clazz.getName());
}
}

private static void addSuperClassesAndInterfaces(Class<?> clazz, Set<ClassLevel> result, int level) {
// Add all superinterfaces
for (Class<?> iface : clazz.getInterfaces()) {
result.add(iface);
addSuperClassesAndInterfaces(iface, result);
// Performance speed up, skip interfaces that are too general
if (iface != Serializable.class && iface != Cloneable.class && iface != Comparable.class) {
result.add(new ClassLevel(iface, level));
addSuperClassesAndInterfaces(iface, result, level + 1);
}
}

// Add superclass
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && superClass != Object.class) {
result.add(superClass);
addSuperClassesAndInterfaces(superClass, result);
result.add(new ClassLevel(superClass, level));
addSuperClassesAndInterfaces(superClass, result, level + 1);
}
}

Expand Down Expand Up @@ -1336,7 +1388,7 @@ public Convert<?> addConversion(Class<?> source, Class<?> target, Convert<?> con
target = toPrimitiveWrapperClass(target);
return factory.put(pair(source, target), conversionFunction);
}

public static long localDateToMillis(LocalDate localDate, ZoneId zoneId) {
return localDate.atStartOfDay(zoneId).toInstant().toEpochMilli();
}
Expand All @@ -1349,15 +1401,6 @@ public static long zonedDateTimeToMillis(ZonedDateTime zonedDateTime) {
return zonedDateTime.toInstant().toEpochMilli();
}

private static final BigDecimal bigDecimalMinByte = BigDecimal.valueOf(Byte.MIN_VALUE);
private static final BigDecimal bigDecimalMaxByte = BigDecimal.valueOf(Byte.MAX_VALUE);
private static final BigDecimal bigDecimalMinShort = BigDecimal.valueOf(Short.MIN_VALUE);
private static final BigDecimal bigDecimalMaxShort = BigDecimal.valueOf(Short.MAX_VALUE);
private static final BigDecimal bigDecimalMinInteger = BigDecimal.valueOf(Integer.MIN_VALUE);
private static final BigDecimal bigDecimalMaxInteger = BigDecimal.valueOf(Integer.MAX_VALUE);
private static final BigDecimal bigDecimalMaxLong = BigDecimal.valueOf(Long.MAX_VALUE);
private static final BigDecimal bigDecimalMinLong = BigDecimal.valueOf(Long.MIN_VALUE);

private static Byte strToByte(String s)
{
Long value = strToLong(s, bigDecimalMinByte, bigDecimalMaxByte);
Expand Down Expand Up @@ -1423,6 +1466,4 @@ private static <T> T identity(T one, Converter converter, ConverterOptions optio
private static String toString(Object one, Converter converter, ConverterOptions options) {
return one.toString();
}


}

0 comments on commit 154b091

Please sign in to comment.