Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowed converter overrides from outside #97

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/main/java/com/cedarsoftware/util/ClassUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ClassUtilities
{
private static final Set<Class<?>> prims = new HashSet<>();

private static final Map<Class<?>, Class<?>> primitiveToWrapper = new HashMap<>(20, .8f);
private static final Map<String, Class<?>> nameToClass = new HashMap<>();

static
Expand All @@ -65,6 +66,16 @@ public class ClassUtilities
nameToClass.put("date", Date.class);
nameToClass.put("class", Class.class);

primitiveToWrapper.put(int.class, Integer.class);
primitiveToWrapper.put(long.class, Long.class);
primitiveToWrapper.put(double.class, Double.class);
primitiveToWrapper.put(float.class, Float.class);
primitiveToWrapper.put(boolean.class, Boolean.class);
primitiveToWrapper.put(char.class, Character.class);
primitiveToWrapper.put(byte.class, Byte.class);
primitiveToWrapper.put(short.class, Short.class);
primitiveToWrapper.put(void.class, Void.class);

}

/**
Expand Down Expand Up @@ -307,4 +318,20 @@ public static boolean areAllConstructorsPrivate(Class<?> c) {

return true;
}

public static Class<?> toPrimitiveWrapperClass(Class<?> primitiveClass) {
if (!primitiveClass.isPrimitive()) {
return primitiveClass;
}

Class<?> c = primitiveToWrapper.get(primitiveClass);

if (c == null) {
throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class");
}

return c;
}


}
48 changes: 11 additions & 37 deletions src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.AbstractMap;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
Expand All @@ -35,6 +34,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.cedarsoftware.util.ClassUtilities;

/**
* Instance conversion utility. Convert from primitive to other primitives, plus support for Number, Date,
* TimeStamp, SQL Date, LocalDate, LocalDateTime, ZonedDateTime, Calendar, Big*, Atomic*, Class, UUID,
Expand Down Expand Up @@ -78,7 +79,6 @@ public final class Converter {
private final ConverterOptions options;

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<?>> CONVERSION_DB = new ConcurrentHashMap<>(500, .8f);

// Create a Map.Entry (pair) of source class to target class.
Expand All @@ -87,26 +87,13 @@ static Map.Entry<Class<?>, Class<?>> pair(Class<?> source, Class<?> target) {
}

static {
buildPrimitiveWrappers();
buildFactoryConversions();
}

public ConverterOptions getOptions() {
return options;
}

private static void buildPrimitiveWrappers() {
primitiveToWrapper.put(int.class, Integer.class);
primitiveToWrapper.put(long.class, Long.class);
primitiveToWrapper.put(double.class, Double.class);
primitiveToWrapper.put(float.class, Float.class);
primitiveToWrapper.put(boolean.class, Boolean.class);
primitiveToWrapper.put(char.class, Character.class);
primitiveToWrapper.put(byte.class, Byte.class);
primitiveToWrapper.put(short.class, Short.class);
primitiveToWrapper.put(void.class, Void.class);
}

private static void buildFactoryConversions() {
// toByte
CONVERSION_DB.put(pair(Void.class, byte.class), NumberConversions::toByteZero);
Expand Down Expand Up @@ -859,6 +846,7 @@ private static void buildFactoryConversions() {
public Converter(ConverterOptions options) {
this.options = options;
this.factory = new ConcurrentHashMap<>(CONVERSION_DB);
this.factory.putAll(this.options.getConverterOverrides());
}

/**
Expand Down Expand Up @@ -901,7 +889,7 @@ public <T> T convert(Object from, Class<T> toType) {
// Promote primitive to primitive wrapper, so we don't have to define so many duplicates in the factory map.
sourceType = from.getClass();
if (toType.isPrimitive()) {
toType = (Class<T>) toPrimitiveWrapperClass(toType);
toType = (Class<T>) ClassUtilities.toPrimitiveWrapperClass(toType);
}
}

Expand Down Expand Up @@ -1038,8 +1026,8 @@ static private String name(Object from) {
* @return boolean true if the Converter converts from the source type to the destination type, false otherwise.
*/
boolean isDirectConversionSupportedFor(Class<?> source, Class<?> target) {
source = toPrimitiveWrapperClass(source);
target = toPrimitiveWrapperClass(target);
source = ClassUtilities.toPrimitiveWrapperClass(source);
target = ClassUtilities.toPrimitiveWrapperClass(target);
Convert<?> method = factory.get(pair(source, target));
return method != null && method != UNSUPPORTED;
}
Expand All @@ -1052,8 +1040,8 @@ boolean isDirectConversionSupportedFor(Class<?> source, Class<?> target) {
* @return boolean true if the Converter converts from the source type to the destination type, false otherwise.
*/
public boolean isConversionSupportedFor(Class<?> source, Class<?> target) {
source = toPrimitiveWrapperClass(source);
target = toPrimitiveWrapperClass(target);
source = ClassUtilities.toPrimitiveWrapperClass(source);
target = ClassUtilities.toPrimitiveWrapperClass(target);
Convert<?> method = factory.get(pair(source, target));
if (method != null && method != UNSUPPORTED) {
return true;
Expand Down Expand Up @@ -1104,28 +1092,14 @@ public Map<String, Set<String>> getSupportedConversions() {
* @return prior conversion function if one existed.
*/
public Convert<?> addConversion(Class<?> source, Class<?> target, Convert<?> conversionFunction) {
source = toPrimitiveWrapperClass(source);
target = toPrimitiveWrapperClass(target);
source = ClassUtilities.toPrimitiveWrapperClass(source);
target = ClassUtilities.toPrimitiveWrapperClass(target);
return factory.put(pair(source, target), conversionFunction);
}

/**
* Given a primitive class, return the Wrapper class equivalent.
*/
static Class<?> toPrimitiveWrapperClass(Class<?> primitiveClass) {
if (!primitiveClass.isPrimitive()) {
return primitiveClass;
}

Class<?> c = primitiveToWrapper.get(primitiveClass);

if (c == null) {
throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class");
}

return c;
}

private static <T> T identity(T from, Converter converter) {
return from;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

/**
Expand Down Expand Up @@ -68,4 +70,10 @@ public interface ConverterOptions {
* @return the Character representing false
*/
default Character falseChar() { return CommonValues.CHARACTER_ZERO; }

/**
* Overrides for converter conversions..
* @return The Map of overrides.
*/
default Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> getConverterOverrides() { return new HashMap<>(); }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package com.cedarsoftware.util.convert;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand All @@ -28,13 +24,19 @@ public class DefaultConverterOptions implements ConverterOptions {

private final Map<String, Object> customOptions;

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

public DefaultConverterOptions() {
this.customOptions = new ConcurrentHashMap<>();
this.converterOverrides = new ConcurrentHashMap<>();
}

@SuppressWarnings("unchecked")
@Override
public <T> T getCustomOption(String name) {
return (T) this.customOptions.get(name);
}

@Override
public Map<Map.Entry<Class<?>, Class<?>>, Convert<?>> getConverterOverrides() { return this.converterOverrides; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import com.cedarsoftware.util.ClassUtilities;

import static com.cedarsoftware.util.MapUtilities.mapOf;
import static com.cedarsoftware.util.convert.Converter.getShortName;
import static com.cedarsoftware.util.convert.Converter.pair;
Expand Down Expand Up @@ -1676,9 +1678,9 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source,
if (source == null) {
assertEquals(sourceClass, Void.class, "On the source-side of test input, null can only appear in the Void.class data");
} else {
assertTrue(Converter.toPrimitiveWrapperClass(sourceClass).isInstance(source), "source type mismatch");
assertTrue(ClassUtilities.toPrimitiveWrapperClass(sourceClass).isInstance(source), "source type mismatch");
}
assertTrue(target == null || target instanceof Throwable || Converter.toPrimitiveWrapperClass(targetClass).isInstance(target), "target type mismatch");
assertTrue(target == null || target instanceof Throwable || ClassUtilities.toPrimitiveWrapperClass(targetClass).isInstance(target), "target type mismatch");

// if the source/target are the same Class, then ensure identity lambda is used.
if (sourceClass.equals(targetClass)) {
Expand Down
Loading