Skip to content

Commit

Permalink
Unsupported conversions can be explicitly added to prevent an inherit…
Browse files Browse the repository at this point in the history
…ed type (like Number) from allowing all derivate types to become supported conversions. For example, Long is a Number and Long can convert to/from a Date. However, Byte is a Number, but it should not convert to/from a Date. By adding an explicit UNSUPPORTED conversion, it will prevent it. Furthermore, the allSupported/getSupported conversions will filter these out so they are not adverstised as available conversions.
  • Loading branch information
jdereg committed Feb 3, 2024
1 parent c65daee commit e89994d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 26 deletions.
6 changes: 6 additions & 0 deletions src/main/java/com/cedarsoftware/util/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -454,27 +454,33 @@ public static AtomicBoolean convertToAtomicBoolean(Object fromInstance)
}

/**
* No longer needed - use convert(localDate, long.class)
* @param localDate A Java LocalDate
* @return a long representing the localDate as epoch milliseconds (since 1970 Jan 1 at midnight)
*/
@Deprecated
public static long localDateToMillis(LocalDate localDate)
{
return instance.convert(localDate, long.class);
}

/**
* No longer needed - use convert(localDateTime, long.class)
* @param localDateTime A Java LocalDateTime
* @return a long representing the localDateTime as epoch milliseconds (since 1970 Jan 1 at midnight)
*/
@Deprecated
public static long localDateTimeToMillis(LocalDateTime localDateTime)
{
return instance.convert(localDateTime, long.class);
}

/**
* No longer needed - use convert(ZonedDateTime, long.class)
* @param zonedDateTime A Java ZonedDateTime
* @return a long representing the ZonedDateTime as epoch milliseconds (since 1970 Jan 1 at midnight)
*/
@Deprecated
public static long zonedDateTimeToMillis(ZonedDateTime zonedDateTime)
{
return instance.convert(zonedDateTime, long.class);
Expand Down
77 changes: 64 additions & 13 deletions src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
*/

public final class Converter {
static final Convert<?> UNSUPPORTED = Converter::unsupported;
static final String VALUE = "_v";

private final Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> factory;
Expand Down Expand Up @@ -112,7 +113,6 @@ private static void buildFactoryConversions() {
DEFAULT_FACTORY.put(pair(Double.class, Byte.class), NumberConversions::toByte);
DEFAULT_FACTORY.put(pair(Boolean.class, Byte.class), BooleanConversions::toByte);
DEFAULT_FACTORY.put(pair(Character.class, Byte.class), CharacterConversions::toByte);
DEFAULT_FACTORY.put(pair(Calendar.class, Byte.class), NumberConversions::toByte);
DEFAULT_FACTORY.put(pair(AtomicBoolean.class, Byte.class), AtomicBooleanConversions::toByte);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, Byte.class), NumberConversions::toByte);
DEFAULT_FACTORY.put(pair(AtomicLong.class, Byte.class), NumberConversions::toByte);
Expand Down Expand Up @@ -267,7 +267,7 @@ private static void buildFactoryConversions() {
DEFAULT_FACTORY.put(pair(String.class, Boolean.class), StringConversions::toBoolean);
DEFAULT_FACTORY.put(pair(Year.class, Boolean.class), YearConversions::toBoolean);

// Character/chat conversions supported
// Character/char conversions supported
DEFAULT_FACTORY.put(pair(Void.class, char.class), VoidConversions::toChar);
DEFAULT_FACTORY.put(pair(Void.class, Character.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, Character.class), NumberConversions::toCharacter);
Expand Down Expand Up @@ -419,10 +419,14 @@ private static void buildFactoryConversions() {

// Date conversions supported
DEFAULT_FACTORY.put(pair(Void.class, Date.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, Date.class), NumberConversions::toDate);
DEFAULT_FACTORY.put(pair(Double.class, Date.class), NumberConversions::toDate);
DEFAULT_FACTORY.put(pair(BigInteger.class, Date.class), NumberConversions::toDate);
DEFAULT_FACTORY.put(pair(BigDecimal.class, Date.class), NumberConversions::toDate);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, Date.class), NumberConversions::toDate);
DEFAULT_FACTORY.put(pair(Date.class, Date.class), DateConversions::toDate);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, Date.class), DateConversions::toDate);
Expand All @@ -439,10 +443,14 @@ private static void buildFactoryConversions() {

// java.sql.Date conversion supported
DEFAULT_FACTORY.put(pair(Void.class, java.sql.Date.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, java.sql.Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, java.sql.Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, java.sql.Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, java.sql.Date.class), NumberConversions::toSqlDate);
DEFAULT_FACTORY.put(pair(Double.class, java.sql.Date.class), NumberConversions::toSqlDate);
DEFAULT_FACTORY.put(pair(BigInteger.class, java.sql.Date.class), NumberConversions::toSqlDate);
DEFAULT_FACTORY.put(pair(BigDecimal.class, java.sql.Date.class), NumberConversions::toSqlDate);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, java.sql.Date.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, java.sql.Date.class), NumberConversions::toSqlDate);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, java.sql.Date.class), DateConversions::toSqlDate);
DEFAULT_FACTORY.put(pair(Date.class, java.sql.Date.class), DateConversions::toSqlDate);
Expand All @@ -459,10 +467,14 @@ private static void buildFactoryConversions() {

// Timestamp conversions supported
DEFAULT_FACTORY.put(pair(Void.class, Timestamp.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, Timestamp.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, Timestamp.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, Timestamp.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, Timestamp.class), NumberConversions::toTimestamp);
DEFAULT_FACTORY.put(pair(Double.class, Timestamp.class), NumberConversions::toTimestamp);
DEFAULT_FACTORY.put(pair(BigInteger.class, Timestamp.class), NumberConversions::toTimestamp);
DEFAULT_FACTORY.put(pair(BigDecimal.class, Timestamp.class), NumberConversions::toTimestamp);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, Timestamp.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, Timestamp.class), NumberConversions::toTimestamp);
DEFAULT_FACTORY.put(pair(Timestamp.class, Timestamp.class), DateConversions::toTimestamp);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, Timestamp.class), DateConversions::toTimestamp);
Expand All @@ -479,10 +491,14 @@ private static void buildFactoryConversions() {

// Calendar conversions supported
DEFAULT_FACTORY.put(pair(Void.class, Calendar.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, Calendar.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, Calendar.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, Calendar.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, Calendar.class), NumberConversions::toCalendar);
DEFAULT_FACTORY.put(pair(Double.class, Calendar.class), NumberConversions::toCalendar);
DEFAULT_FACTORY.put(pair(BigInteger.class, Calendar.class), NumberConversions::toCalendar);
DEFAULT_FACTORY.put(pair(BigDecimal.class, Calendar.class), NumberConversions::toCalendar);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, Calendar.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, Calendar.class), NumberConversions::toCalendar);
DEFAULT_FACTORY.put(pair(Date.class, Calendar.class), DateConversions::toCalendar);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, Calendar.class), DateConversions::toCalendar);
Expand All @@ -499,10 +515,14 @@ private static void buildFactoryConversions() {

// LocalDate conversions supported
DEFAULT_FACTORY.put(pair(Void.class, LocalDate.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, LocalDate.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, LocalDate.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, LocalDate.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, LocalDate.class), NumberConversions::toLocalDate);
DEFAULT_FACTORY.put(pair(Double.class, LocalDate.class), NumberConversions::toLocalDate);
DEFAULT_FACTORY.put(pair(BigInteger.class, LocalDate.class), NumberConversions::toLocalDate);
DEFAULT_FACTORY.put(pair(BigDecimal.class, LocalDate.class), NumberConversions::toLocalDate);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, LocalDate.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, LocalDate.class), NumberConversions::toLocalDate);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, LocalDate.class), DateConversions::toLocalDate);
DEFAULT_FACTORY.put(pair(Timestamp.class, LocalDate.class), DateConversions::toLocalDate);
Expand All @@ -519,10 +539,14 @@ private static void buildFactoryConversions() {

// LocalDateTime conversions supported
DEFAULT_FACTORY.put(pair(Void.class, LocalDateTime.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, LocalDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, LocalDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, LocalDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, LocalDateTime.class), NumberConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(Double.class, LocalDateTime.class), NumberConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(BigInteger.class, LocalDateTime.class), NumberConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(BigDecimal.class, LocalDateTime.class), NumberConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, LocalDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, LocalDateTime.class), NumberConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, LocalDateTime.class), DateConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(Timestamp.class, LocalDateTime.class), DateConversions::toLocalDateTime);
Expand All @@ -539,10 +563,14 @@ private static void buildFactoryConversions() {

// LocalTime conversions supported
DEFAULT_FACTORY.put(pair(Void.class, LocalTime.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, LocalTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, LocalTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, LocalTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, LocalTime.class), NumberConversions::toLocalTime);
DEFAULT_FACTORY.put(pair(Double.class, LocalTime.class), NumberConversions::toLocalTime);
DEFAULT_FACTORY.put(pair(BigInteger.class, LocalTime.class), NumberConversions::toLocalTime);
DEFAULT_FACTORY.put(pair(BigDecimal.class, LocalTime.class), NumberConversions::toLocalDateTime);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, LocalTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, LocalTime.class), NumberConversions::toLocalTime);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, LocalTime.class), DateConversions::toLocalTime);
DEFAULT_FACTORY.put(pair(Timestamp.class, LocalTime.class), DateConversions::toLocalTime);
Expand All @@ -560,10 +588,14 @@ private static void buildFactoryConversions() {

// ZonedDateTime conversions supported
DEFAULT_FACTORY.put(pair(Void.class, ZonedDateTime.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Byte.class, ZonedDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, ZonedDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, ZonedDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, ZonedDateTime.class), NumberConversions::toZonedDateTime);
DEFAULT_FACTORY.put(pair(Double.class, ZonedDateTime.class), NumberConversions::toZonedDateTime);
DEFAULT_FACTORY.put(pair(BigInteger.class, ZonedDateTime.class), NumberConversions::toZonedDateTime);
DEFAULT_FACTORY.put(pair(BigDecimal.class, ZonedDateTime.class), NumberConversions::toZonedDateTime);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, ZonedDateTime.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, ZonedDateTime.class), NumberConversions::toZonedDateTime);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, ZonedDateTime.class), DateConversions::toZonedDateTime);
DEFAULT_FACTORY.put(pair(Timestamp.class, ZonedDateTime.class), DateConversions::toZonedDateTime);
Expand Down Expand Up @@ -657,10 +689,14 @@ private static void buildFactoryConversions() {
// Instant conversions supported
DEFAULT_FACTORY.put(pair(Void.class, Instant.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Instant.class, Instant.class), Converter::identity);
DEFAULT_FACTORY.put(pair(Byte.class, Instant.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Short.class, Instant.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Integer.class, Instant.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Long.class, Instant.class), NumberConversions::toInstant);
DEFAULT_FACTORY.put(pair(Double.class, Instant.class), NumberConversions::toInstant);
DEFAULT_FACTORY.put(pair(BigInteger.class, Instant.class), NumberConversions::toInstant);
DEFAULT_FACTORY.put(pair(BigDecimal.class, Instant.class), NumberConversions::toInstant);
DEFAULT_FACTORY.put(pair(AtomicInteger.class, Instant.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(AtomicLong.class, Instant.class), NumberConversions::toInstant);
DEFAULT_FACTORY.put(pair(java.sql.Date.class, Instant.class), DateConversions::toInstant);
DEFAULT_FACTORY.put(pair(Timestamp.class, Instant.class), DateConversions::toInstant);
Expand Down Expand Up @@ -762,6 +798,7 @@ private static void buildFactoryConversions() {
// toYear
DEFAULT_FACTORY.put(pair(Void.class, Year.class), VoidConversions::toNull);
DEFAULT_FACTORY.put(pair(Year.class, Year.class), Converter::identity);
DEFAULT_FACTORY.put(pair(Byte.class, Year.class), UNSUPPORTED);
DEFAULT_FACTORY.put(pair(Number.class, Year.class), NumberConversions::toYear);
DEFAULT_FACTORY.put(pair(String.class, Year.class), StringConversions::toYear);
DEFAULT_FACTORY.put(pair(Map.class, Year.class), MapConversions::toYear);
Expand Down Expand Up @@ -883,13 +920,13 @@ public <T> T convert(Object from, Class<T> toType, ConverterOptions options) {

// Direct Mapping
Convert<?> converter = factory.get(pair(sourceType, toType));
if (converter != null) {
if (converter != null && converter != UNSUPPORTED) {
return (T) converter.convert(from, this, options);
}

// Try inheritance
converter = getInheritedConverter(sourceType, toType);
if (converter != null) {
if (converter != null && converter != UNSUPPORTED) {
// Fast lookup next time.
if (!isDirectConversionSupportedFor(sourceType, toType)) {
addConversion(sourceType, toType, converter);
Expand Down Expand Up @@ -1016,7 +1053,8 @@ static private String name(Object from) {
boolean isDirectConversionSupportedFor(Class<?> source, Class<?> target) {
source = toPrimitiveWrapperClass(source);
target = toPrimitiveWrapperClass(target);
return factory.containsKey(pair(source, target));
Convert<?> method = factory.get(pair(source, target));
return method != null && method != UNSUPPORTED;
}

/**
Expand All @@ -1029,10 +1067,13 @@ boolean isDirectConversionSupportedFor(Class<?> source, Class<?> target) {
public boolean isConversionSupportedFor(Class<?> source, Class<?> target) {
source = toPrimitiveWrapperClass(source);
target = toPrimitiveWrapperClass(target);
if (factory.containsKey(pair(source, target))) {
Convert<?> method = factory.get(pair(source, target));
if (method != null && method != UNSUPPORTED) {
return true;
}
return getInheritedConverter(source, target) != null;

method = getInheritedConverter(source, target);
return method != null && method != UNSUPPORTED;
}

/**
Expand All @@ -1042,8 +1083,11 @@ 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()));

for (Map.Entry<Class<?>, Class<?>> pairs : factory.keySet()) {
toFrom.computeIfAbsent(pairs.getKey(), k -> new TreeSet<>((c1, c2) -> c1.getName().compareToIgnoreCase(c2.getName()))).add(pairs.getValue());
for (Map.Entry<Map.Entry<Class<?>, Class<?>>, Convert<?>> entry : factory.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 @@ -1055,8 +1099,11 @@ public Map<Class<?>, Set<Class<?>>> allSupportedConversions() {
public Map<String, Set<String>> getSupportedConversions() {
Map<String, Set<String>> toFrom = new TreeMap<>(String::compareToIgnoreCase);

for (Map.Entry<Class<?>, Class<?>> pairs : factory.keySet()) {
toFrom.computeIfAbsent(getShortName(pairs.getKey()), k -> new TreeSet<>(String::compareToIgnoreCase)).add(getShortName(pairs.getValue()));
for (Map.Entry<Map.Entry<Class<?>, Class<?>>, Convert<?>> entry : factory.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 Down Expand Up @@ -1092,7 +1139,11 @@ private static Class<?> toPrimitiveWrapperClass(Class<?> primitiveClass) {
return c;
}

private static <T> T identity(T one, Converter converter, ConverterOptions options) {
return one;
private static <T> T identity(T from, Converter converter, ConverterOptions options) {
return from;
}

private static <T> T unsupported(T from, Converter converter, ConverterOptions options) {
return (T) UNSUPPORTED;
}
}
Loading

0 comments on commit e89994d

Please sign in to comment.