From afbfdfa13ee8a7700e35a52fa1b03c5d9f3ff4b2 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 3 Apr 2020 17:44:48 +0100 Subject: [PATCH] Single dimension converters --- .../converters/api/ConvertedType.java | 7 + .../io/smallrye/converters/api/Converter.java | 5 +- .../smallrye/converters/api/Converters.java | 13 +- .../converters/api/ConvertersProvider.java | 15 ++ .../api/ConvertersProviderResolver.java | 40 +++++ .../converters/api/InputConverter.java | 6 + .../converters/api/OutputConverter.java | 6 + implementation/pom.xml | 20 +++ .../converters/ConvertedTypeImpl.java | 101 ++++++++++++ .../{Converters.java => ConvertersUtils.java} | 4 +- .../converters/SmallRyeConverters.java | 65 ++++++-- .../converters/SmallRyeConvertersBuilder.java | 33 +++- .../SmallRyeConvertersProviderResolver.java | 83 ++++++++++ ....converters.api.ConvertersProviderResolver | 1 + .../converters/ConvertedTypeTest.java | 144 ++++++++++++++++++ .../converters/ConvertersProviderTest.java | 13 ++ .../ConvertersStringCleanupTest.java | 14 +- .../smallrye/converters/ConvertersTest.java | 38 ++--- .../converters/CustomConverterTest.java | 44 +++--- .../converters/ImplicitConverterTest.java | 11 +- .../SmallRyeConvertersBuilderTest.java | 8 +- .../converters/SmallRyeConvertersTest.java | 127 ++++++++++++++- 22 files changed, 703 insertions(+), 95 deletions(-) create mode 100644 api/src/main/java/io/smallrye/converters/api/ConvertedType.java create mode 100644 api/src/main/java/io/smallrye/converters/api/ConvertersProvider.java create mode 100644 api/src/main/java/io/smallrye/converters/api/ConvertersProviderResolver.java create mode 100644 api/src/main/java/io/smallrye/converters/api/InputConverter.java create mode 100644 api/src/main/java/io/smallrye/converters/api/OutputConverter.java create mode 100644 implementation/src/main/java/io/smallrye/converters/ConvertedTypeImpl.java rename implementation/src/main/java/io/smallrye/converters/{Converters.java => ConvertersUtils.java} (99%) create mode 100644 implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersProviderResolver.java create mode 100644 implementation/src/main/resources/META-INF/services/io.smallrye.converters.api.ConvertersProviderResolver create mode 100644 implementation/src/test/java/io/smallrye/converters/ConvertedTypeTest.java create mode 100644 implementation/src/test/java/io/smallrye/converters/ConvertersProviderTest.java diff --git a/api/src/main/java/io/smallrye/converters/api/ConvertedType.java b/api/src/main/java/io/smallrye/converters/api/ConvertedType.java new file mode 100644 index 0000000..957631b --- /dev/null +++ b/api/src/main/java/io/smallrye/converters/api/ConvertedType.java @@ -0,0 +1,7 @@ +package io.smallrye.converters.api; + +public interface ConvertedType { + S getRaw(); + + T getAs(Class klass); +} diff --git a/api/src/main/java/io/smallrye/converters/api/Converter.java b/api/src/main/java/io/smallrye/converters/api/Converter.java index 85d97eb..073697d 100644 --- a/api/src/main/java/io/smallrye/converters/api/Converter.java +++ b/api/src/main/java/io/smallrye/converters/api/Converter.java @@ -1,7 +1,6 @@ package io.smallrye.converters.api; -import java.io.Serializable; - -public interface Converter extends Serializable { +@FunctionalInterface +public interface Converter { T convert(String value); } diff --git a/api/src/main/java/io/smallrye/converters/api/Converters.java b/api/src/main/java/io/smallrye/converters/api/Converters.java index 12e98ee..2042bdd 100644 --- a/api/src/main/java/io/smallrye/converters/api/Converters.java +++ b/api/src/main/java/io/smallrye/converters/api/Converters.java @@ -1,20 +1,21 @@ package io.smallrye.converters.api; -import java.io.Serializable; import java.util.Collection; import java.util.Optional; import java.util.function.IntFunction; -public interface Converters extends Serializable { +public interface Converters { Converter getConverter(Class asType); Converter> getOptionalConverter(Class asType); - T convertValue(String value, Class asType); + T convert(String value, Class asType); - T convertValue(String value, Converter converter); + T convert(String value, Converter converter); - > C convertValues(String value, Class asType, IntFunction collectionFactory); + > C convert(String value, Class asType, IntFunction collectionFactory); - > C convertValues(String value, Converter converter, IntFunction collectionFactory); + > C convert(String value, Converter converter, IntFunction collectionFactory); + + ConvertedType from(S value); } diff --git a/api/src/main/java/io/smallrye/converters/api/ConvertersProvider.java b/api/src/main/java/io/smallrye/converters/api/ConvertersProvider.java new file mode 100644 index 0000000..b84c229 --- /dev/null +++ b/api/src/main/java/io/smallrye/converters/api/ConvertersProvider.java @@ -0,0 +1,15 @@ +package io.smallrye.converters.api; + +public final class ConvertersProvider { + private ConvertersProvider() { + throw new UnsupportedOperationException(); + } + + public static Converters getConverters() { + return ConvertersProviderResolver.instance().getConverters(); + } + + public static Converters getConverters(final ClassLoader classLoader) { + return ConvertersProviderResolver.instance().getConverters(classLoader); + } +} diff --git a/api/src/main/java/io/smallrye/converters/api/ConvertersProviderResolver.java b/api/src/main/java/io/smallrye/converters/api/ConvertersProviderResolver.java new file mode 100644 index 0000000..ca447c6 --- /dev/null +++ b/api/src/main/java/io/smallrye/converters/api/ConvertersProviderResolver.java @@ -0,0 +1,40 @@ +package io.smallrye.converters.api; + +import java.util.Iterator; +import java.util.ServiceLoader; + +public abstract class ConvertersProviderResolver { + private static volatile ConvertersProviderResolver instance = null; + + public abstract Converters getConverters(); + + public abstract Converters getConverters(ClassLoader classLoader); + + protected ConvertersProviderResolver() { + + } + + public static ConvertersProviderResolver instance() { + if (instance == null) { + synchronized (ConvertersProviderResolver.class) { + if (instance != null) { + return instance; + } + instance = loadSpi(ConvertersProviderResolver.class.getClassLoader()); + } + } + + return instance; + } + + private static ConvertersProviderResolver loadSpi(ClassLoader cl) { + ServiceLoader sl = ServiceLoader.load( + ConvertersProviderResolver.class, cl); + final Iterator iterator = sl.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } + throw new IllegalStateException( + "No ConvertersProviderResolver implementation found!"); + } +} diff --git a/api/src/main/java/io/smallrye/converters/api/InputConverter.java b/api/src/main/java/io/smallrye/converters/api/InputConverter.java new file mode 100644 index 0000000..9f18de1 --- /dev/null +++ b/api/src/main/java/io/smallrye/converters/api/InputConverter.java @@ -0,0 +1,6 @@ +package io.smallrye.converters.api; + +@FunctionalInterface +public interface InputConverter { + T convert(S value); +} diff --git a/api/src/main/java/io/smallrye/converters/api/OutputConverter.java b/api/src/main/java/io/smallrye/converters/api/OutputConverter.java new file mode 100644 index 0000000..7c9ac54 --- /dev/null +++ b/api/src/main/java/io/smallrye/converters/api/OutputConverter.java @@ -0,0 +1,6 @@ +package io.smallrye.converters.api; + +@FunctionalInterface +public interface OutputConverter { + T convert(ConvertedType value); +} diff --git a/implementation/pom.xml b/implementation/pom.xml index cc2c65a..97998db 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -51,6 +51,26 @@ org.junit.jupiter junit-jupiter + + + jakarta.json.bind + jakarta.json.bind-api + test + + + + org.eclipse + yasson + 1.0.6 + test + + + + org.yaml + snakeyaml + 1.26 + test + diff --git a/implementation/src/main/java/io/smallrye/converters/ConvertedTypeImpl.java b/implementation/src/main/java/io/smallrye/converters/ConvertedTypeImpl.java new file mode 100644 index 0000000..88496cb --- /dev/null +++ b/implementation/src/main/java/io/smallrye/converters/ConvertedTypeImpl.java @@ -0,0 +1,101 @@ +package io.smallrye.converters; + +import java.lang.reflect.Type; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import io.smallrye.converters.api.ConvertedType; +import io.smallrye.converters.api.Converter; +import io.smallrye.converters.api.Converters; +import io.smallrye.converters.api.ConvertersProvider; +import io.smallrye.converters.api.OutputConverter; + +public class ConvertedTypeImpl implements ConvertedType { + private final S value; + private final Map> converters; + + public ConvertedTypeImpl(final S value, final Map> converters) { + assert value != null; + + this.value = value; + this.converters = converters; + } + + public Object getValue() { + return value; + } + + public String getAsString() { + return value.toString(); + } + + public Number getAsNumber() { + return getAs(Double.class); + } + + public Boolean getAsBoolean() { + return getAs(Boolean.class); + } + + @Override + public S getRaw() { + return value; + } + + @Override + @SuppressWarnings("unchecked") + public T getAs(Class klass) { + if (klass.isInstance(value)) { + return (T) value; + } + + OutputConverter converter = (OutputConverter) converters.get(klass); + if (converter == null) { + throw ConverterMessages.msg.noRegisteredConverter(klass); + } + + return converter.convert((this)); + } + + @SuppressWarnings("unchecked") + public List getAsList(Class klass) { + if (List.class.isAssignableFrom(value.getClass())) { + return (List) ((List) value).stream() + .map(o -> ConvertedTypeImpl.of(o).getAs(klass)) + .collect(Collectors.toList()); + } + + if (value instanceof String) { + return (List) ConvertersUtils.newCollectionConverter( + (Converter) value -> ConvertedTypeImpl.of(value).getAs(klass), ArrayList::new) + .convert((String) value); + } + + throw new IllegalStateException(); + } + + public Map getAsMap(Class key, Class value) { + if (Map.class.isAssignableFrom(this.value.getClass())) { + return ((Set) ((Map) this.value).entrySet()) + .stream() + .map(entry -> new AbstractMap.SimpleEntry<>(ConvertedTypeImpl.of(entry.getKey()).getAs(key), + ConvertedTypeImpl.of(entry.getValue()).getAs(value))) + .collect(Collectors.toMap(entry -> (K) entry.getKey(), + entry -> (V) entry.getValue())); + } + + throw new IllegalStateException(); + } + + static ConvertedTypeImpl of(final S rawValue) { + return of(rawValue, ConvertersProvider.getConverters()); + } + + static ConvertedTypeImpl of(final S rawValue, final Converters converters) { + throw new UnsupportedOperationException(); + } +} diff --git a/implementation/src/main/java/io/smallrye/converters/Converters.java b/implementation/src/main/java/io/smallrye/converters/ConvertersUtils.java similarity index 99% rename from implementation/src/main/java/io/smallrye/converters/Converters.java rename to implementation/src/main/java/io/smallrye/converters/ConvertersUtils.java index b193f03..f5d8aed 100644 --- a/implementation/src/main/java/io/smallrye/converters/Converters.java +++ b/implementation/src/main/java/io/smallrye/converters/ConvertersUtils.java @@ -47,8 +47,8 @@ * * @author Jeff Mesnil (c) 2017 Red Hat inc. */ -public final class Converters { - private Converters() { +public final class ConvertersUtils { + private ConvertersUtils() { } static final Converter STRING_CONVERTER = BuiltInConverter.of(0, newEmptyValueConverter(value -> value)); diff --git a/implementation/src/main/java/io/smallrye/converters/SmallRyeConverters.java b/implementation/src/main/java/io/smallrye/converters/SmallRyeConverters.java index 9275879..c80c154 100644 --- a/implementation/src/main/java/io/smallrye/converters/SmallRyeConverters.java +++ b/implementation/src/main/java/io/smallrye/converters/SmallRyeConverters.java @@ -2,25 +2,36 @@ import java.lang.reflect.Type; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.IntFunction; +import io.smallrye.converters.api.ConvertedType; import io.smallrye.converters.api.Converter; +import io.smallrye.converters.api.InputConverter; +import io.smallrye.converters.api.OutputConverter; public class SmallRyeConverters implements io.smallrye.converters.api.Converters { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); + private final Map> inputConverters = new HashMap<>(); + private final Map> outputConverters = new HashMap<>(); + SmallRyeConverters() { - converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS); + converters = new ConcurrentHashMap<>(ConvertersUtils.ALL_CONVERTERS); } - SmallRyeConverters(final SmallRyeConvertersBuilder smallRyeConvertersBuilder) { + SmallRyeConverters(final SmallRyeConvertersBuilder builder) { this(); - smallRyeConvertersBuilder.getConverters() + builder.getConverters() .forEach((type, converter) -> this.converters.put(type, converter.getConverter())); + + // TODO - add Priority + inputConverters.putAll(builder.getInputConverters()); + outputConverters.putAll(builder.getOutputConverters()); } @SuppressWarnings("unchecked") @@ -31,11 +42,11 @@ public Converter getConverter(Class asType) { return (Converter) exactConverter; } if (asType.isPrimitive()) { - return (Converter) getConverter(Converters.wrapPrimitiveType(asType)); + return (Converter) getConverter(ConvertersUtils.wrapPrimitiveType(asType)); } if (asType.isArray()) { final Converter conv = getConverter(asType.getComponentType()); - return conv == null ? null : Converters.newArrayConverter(conv, asType); + return conv == null ? null : ConvertersUtils.newArrayConverter(conv, asType); } return (Converter) converters.computeIfAbsent(asType, clazz -> ImplicitConverters.getConverter((Class) clazz)); } @@ -44,28 +55,58 @@ public Converter getConverter(Class asType) { @Override public Converter> getOptionalConverter(Class asType) { return optionalConverters.computeIfAbsent(asType, - clazz -> Converters.newOptionalConverter(getConverter((Class) clazz))); + clazz -> ConvertersUtils.newOptionalConverter(getConverter((Class) clazz))); } @Override - public T convertValue(final String value, final Class asType) { + public T convert(final String value, final Class asType) { return getConverter(asType).convert(value); } @Override - public T convertValue(final String value, final Converter converter) { + public T convert(final String value, final Converter converter) { return converter.convert(value); } @Override - public > C convertValues(final String value, final Class asType, + public > C convert(final String value, final Class asType, final IntFunction collectionFactory) { - return convertValues(value, getConverter(asType), collectionFactory); + return convert(value, getConverter(asType), collectionFactory); } @Override - public > C convertValues(final String value, final Converter converter, + public > C convert(final String value, final Converter converter, final IntFunction collectionFactory) { - return convertValue(value, Converters.newCollectionConverter(converter, collectionFactory)); + return convert(value, ConvertersUtils.newCollectionConverter(converter, collectionFactory)); + } + + @Override + @SuppressWarnings("unchecked") + public ConvertedType from(final S value) { + InputConverter inputConverter = (InputConverter) getInputConverter(value.getClass()); + T input = inputConverter.convert(value); + Map> outputConverters = getOutputConverters(value.getClass()); + return new ConvertedTypeImpl(input, (Map>) outputConverters); + } + + @SuppressWarnings("unchecked") + private InputConverter getInputConverter(final Class asType) { + InputConverter exactConverter = (InputConverter) inputConverters.get(asType); + if (exactConverter != null) { + return (InputConverter) exactConverter; + } + + // passthrough + return value -> (T) value; + } + + @SuppressWarnings("unchecked") + private Map> getOutputConverters(final Class asType) { + Map> converters = new HashMap<>(); + for (Map.Entry> entry : outputConverters.entrySet()) { + // TODO - Filter based on S,T of OutputConverter + converters.put(entry.getKey(), (OutputConverter) entry.getValue()); + } + return converters; } } diff --git a/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersBuilder.java b/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersBuilder.java index c43a6ed..cf61fed 100644 --- a/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersBuilder.java +++ b/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersBuilder.java @@ -7,24 +7,37 @@ import javax.annotation.Priority; import io.smallrye.converters.api.Converter; +import io.smallrye.converters.api.InputConverter; +import io.smallrye.converters.api.OutputConverter; public class SmallRyeConvertersBuilder { - private Map converters = new HashMap<>(); + private final Map converters = new HashMap<>(); + + private final Map> inputConverters = new HashMap<>(); + private final Map> outputConverters = new HashMap<>(); public SmallRyeConvertersBuilder() { } - public Map getConverters() { + Map getConverters() { return converters; } + Map> getInputConverters() { + return inputConverters; + } + + Map> getOutputConverters() { + return outputConverters; + } + public SmallRyeConverters build() { return new SmallRyeConverters(this); } public SmallRyeConvertersBuilder withConverters(Converter[] converters) { for (Converter converter : converters) { - Type type = Converters.getConverterType(converter.getClass()); + Type type = ConvertersUtils.getConverterType(converter.getClass()); if (type == null) { throw ConverterMessages.msg.unableToAddConverter(converter); } @@ -43,6 +56,20 @@ public SmallRyeConvertersBuilder withConverter(Type type, int priority, Conv return this; } + public SmallRyeConvertersBuilder withInputConverter( + Class type, + InputConverter converter) { + this.inputConverters.put(type, converter); + return this; + } + + public SmallRyeConvertersBuilder withOutputConverter( + Class type, + OutputConverter converter) { + this.outputConverters.put(type, converter); + return this; + } + private static void addConverter(Type type, Converter converter, Map converters) { addConverter(type, getPriority(converter), converter, converters); } diff --git a/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersProviderResolver.java b/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersProviderResolver.java new file mode 100644 index 0000000..7e44ea8 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/converters/SmallRyeConvertersProviderResolver.java @@ -0,0 +1,83 @@ +package io.smallrye.converters; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.smallrye.converters.api.Converters; +import io.smallrye.converters.api.ConvertersProviderResolver; + +public class SmallRyeConvertersProviderResolver extends ConvertersProviderResolver { + private final Map convertersForClassLoader = new ConcurrentHashMap<>(); + + static final ClassLoader SYSTEM_CL; + + static { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + SYSTEM_CL = AccessController + .doPrivileged( + (PrivilegedAction) SmallRyeConvertersProviderResolver::calculateSystemClassLoader); + } else { + SYSTEM_CL = calculateSystemClassLoader(); + } + } + + @Override + public Converters getConverters() { + return getConverters(getContextClassLoader()); + } + + @Override + public Converters getConverters(ClassLoader classLoader) { + final ClassLoader realClassLoader = getRealClassLoader(classLoader); + Converters converters = convertersForClassLoader.get(realClassLoader); + if (converters == null) { + synchronized (convertersForClassLoader) { + converters = convertersForClassLoader.get(realClassLoader); + if (converters == null) { + converters = new SmallRyeConvertersBuilder().build(); + convertersForClassLoader.put(realClassLoader, converters); + } + } + } + return converters; + } + + private static ClassLoader calculateSystemClassLoader() { + ClassLoader cl = ClassLoader.getSystemClassLoader(); + if (cl == null) { + // non-null ref that delegates to the system + cl = new ClassLoader(null) { + }; + } + return cl; + } + + private static ClassLoader getContextClassLoader() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } else { + return AccessController.doPrivileged((PrivilegedAction) () -> { + ClassLoader tccl = null; + try { + tccl = Thread.currentThread().getContextClassLoader(); + } catch (SecurityException ex) { + // TODO - log + } + return tccl; + }); + } + } + + private static ClassLoader getRealClassLoader(ClassLoader classLoader) { + if (classLoader == null) { + classLoader = getContextClassLoader(); + } + if (classLoader == null) { + classLoader = SYSTEM_CL; + } + return classLoader; + } +} diff --git a/implementation/src/main/resources/META-INF/services/io.smallrye.converters.api.ConvertersProviderResolver b/implementation/src/main/resources/META-INF/services/io.smallrye.converters.api.ConvertersProviderResolver new file mode 100644 index 0000000..3e7f572 --- /dev/null +++ b/implementation/src/main/resources/META-INF/services/io.smallrye.converters.api.ConvertersProviderResolver @@ -0,0 +1 @@ +io.smallrye.converters.SmallRyeConvertersProviderResolver diff --git a/implementation/src/test/java/io/smallrye/converters/ConvertedTypeTest.java b/implementation/src/test/java/io/smallrye/converters/ConvertedTypeTest.java new file mode 100644 index 0000000..2c75bd3 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/converters/ConvertedTypeTest.java @@ -0,0 +1,144 @@ +package io.smallrye.converters; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import io.smallrye.converters.api.Converters; +import io.smallrye.converters.api.ConvertersProvider; + +@Disabled +class ConvertedTypeTest { + @Test + void stringToNumbers() { + Converters converters = ConvertersProvider.getConverters(); + + assertEquals(1, converters.from("1").getAs(Byte.class).byteValue()); + assertEquals(1, ConvertedTypeImpl.of("1").getAs(Short.class).shortValue()); + assertEquals(1, ConvertedTypeImpl.of("1").getAs(Integer.class).intValue()); + assertEquals(9999999999L, ConvertedTypeImpl.of("9999999999").getAs(Long.class).longValue()); + assertEquals(10.1f, ConvertedTypeImpl.of("10.10").getAs(Float.class), 0); + assertEquals(10.1d, ConvertedTypeImpl.of("10.10").getAs(Double.class), 0); + + assertEquals(1, ConvertedTypeImpl.of("1").getAsNumber().byteValue()); + assertEquals(1, ConvertedTypeImpl.of("1").getAsNumber().shortValue()); + assertEquals(1, ConvertedTypeImpl.of("1").getAsNumber().intValue()); + assertEquals(9999999999L, ConvertedTypeImpl.of("9999999999").getAsNumber().longValue()); + assertEquals(10.1f, ConvertedTypeImpl.of("10.10").getAsNumber().floatValue(), 0); + assertEquals(10.1d, ConvertedTypeImpl.of("10.10").getAsNumber().doubleValue(), 0); + } + + @Test + void numbersToString() { + assertEquals("1", ConvertedTypeImpl.of((byte) 1).getAs(String.class)); + assertEquals("1", ConvertedTypeImpl.of((short) 1).getAs(String.class)); + assertEquals("1", ConvertedTypeImpl.of(1).getAs(String.class)); + assertEquals("9999999999", ConvertedTypeImpl.of(9999999999L).getAs(String.class)); + assertEquals("10.1", ConvertedTypeImpl.of(10.10f).getAs(String.class)); + assertEquals("10.1", ConvertedTypeImpl.of(10.10d).getAs(String.class)); + } + + @Test + void booleans() { + assertTrue(ConvertedTypeImpl.of("true").getAs(Boolean.class)); + assertTrue(ConvertedTypeImpl.of("true").getAsBoolean()); + assertEquals("true", ConvertedTypeImpl.of(Boolean.TRUE).getAsString()); + } + + @Test + void arrays() { + String[] strings = ConvertedTypeImpl.of(new String[] { "1", "2", "3" }).getAs(String[].class); + assertTrue(Arrays.asList(strings).contains("1")); + assertTrue(Arrays.asList(strings).contains("2")); + assertTrue(Arrays.asList(strings).contains("3")); + + /* + * Integer[] integers = ConvertedType.of(new String[]{"1", "2", "3"}).getAs(Integer[].class); + * assertTrue(Arrays.asList(integers).contains(1)); + * assertTrue(Arrays.asList(integers).contains(2)); + * assertTrue(Arrays.asList(integers).contains(3)); + */ + } + + @Test + void lists() { + List strings = ConvertedTypeImpl.of(Stream.of("1", "2", "3").collect(toList())).getAsList(String.class); + assertTrue(strings.contains("1")); + assertTrue(strings.contains("2")); + assertTrue(strings.contains("3")); + + List integers = ConvertedTypeImpl.of(Stream.of(1, 2, 3).collect(toList())).getAsList(Integer.class); + assertTrue(integers.contains(1)); + assertTrue(integers.contains(2)); + assertTrue(integers.contains(3)); + + List intsAsStrings = ConvertedTypeImpl.of(Stream.of(1, 2, 3).collect(toList())).getAsList(String.class); + assertTrue(intsAsStrings.contains("1")); + assertTrue(intsAsStrings.contains("2")); + assertTrue(intsAsStrings.contains("3")); + + List stringsAsInts = ConvertedTypeImpl.of(Stream.of("1", "2", "3").collect(toList())) + .getAsList(Integer.class); + assertTrue(stringsAsInts.contains(1)); + assertTrue(stringsAsInts.contains(2)); + assertTrue(stringsAsInts.contains(3)); + } + + @Test + void stringAsList() { + List strings = ConvertedTypeImpl.of("1,2,3").getAsList(String.class); + assertTrue(strings.contains("1")); + assertTrue(strings.contains("2")); + assertTrue(strings.contains("3")); + + List integers = ConvertedTypeImpl.of("1,2,3").getAsList(Integer.class); + assertTrue(integers.contains(1)); + assertTrue(integers.contains(2)); + assertTrue(integers.contains(3)); + } + + @Test + void maps() { + Map strings = ConvertedTypeImpl.of(new HashMap() { + { + put("1", "a"); + put("2", "b"); + } + }).getAsMap(String.class, String.class); + assertTrue(strings.containsKey("1")); + assertTrue(strings.containsKey("2")); + assertEquals("a", strings.get("1")); + assertEquals("b", strings.get("2")); + + Map stringInts = ConvertedTypeImpl.of(new HashMap() { + { + put("1", "a"); + put("2", "b"); + } + }).getAsMap(Integer.class, String.class); + assertTrue(stringInts.containsKey(1)); + assertTrue(stringInts.containsKey(2)); + assertEquals("a", stringInts.get(1)); + assertEquals("b", stringInts.get(2)); + + Map intsAsStrings = ConvertedTypeImpl.of(new HashMap() { + { + put(1, 1); + put(2, 2); + } + }).getAsMap(String.class, String.class); + assertTrue(intsAsStrings.containsKey("1")); + assertTrue(intsAsStrings.containsKey("2")); + assertEquals("1", intsAsStrings.get("1")); + assertEquals("2", intsAsStrings.get("2")); + } +} diff --git a/implementation/src/test/java/io/smallrye/converters/ConvertersProviderTest.java b/implementation/src/test/java/io/smallrye/converters/ConvertersProviderTest.java new file mode 100644 index 0000000..95c9e33 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/converters/ConvertersProviderTest.java @@ -0,0 +1,13 @@ +package io.smallrye.converters; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.smallrye.converters.api.ConvertersProvider; + +class ConvertersProviderTest { + @Test + void provider() { + Assertions.assertNotNull(ConvertersProvider.getConverters()); + } +} diff --git a/implementation/src/test/java/io/smallrye/converters/ConvertersStringCleanupTest.java b/implementation/src/test/java/io/smallrye/converters/ConvertersStringCleanupTest.java index acfc9b8..a485f06 100644 --- a/implementation/src/test/java/io/smallrye/converters/ConvertersStringCleanupTest.java +++ b/implementation/src/test/java/io/smallrye/converters/ConvertersStringCleanupTest.java @@ -28,6 +28,8 @@ import org.junit.jupiter.params.provider.MethodSource; import io.smallrye.converters.api.Converter; +import io.smallrye.converters.api.Converters; +import io.smallrye.converters.api.ConvertersProvider; class ConvertersStringCleanupTest { static Stream data() { @@ -58,7 +60,7 @@ static Stream data() { @ParameterizedTest(name = "{0} - {2}") @MethodSource("data") void simple(Class type, T expected, String string) { - SmallRyeConverters converters = buildConverters(); + Converters converters = ConvertersProvider.getConverters(); Converter converter = converters.getConverter(type); assertEquals(expected, converter.convert(string)); } @@ -66,7 +68,7 @@ void simple(Class type, T expected, String string) { @ParameterizedTest(name = "{0} - {2}") @MethodSource("data") void trailingSpace(Class type, T expected, String string) { - SmallRyeConverters converters = buildConverters(); + Converters converters = ConvertersProvider.getConverters(); Converter converter = converters.getConverter(type); assertEquals(expected, converter.convert(string + " ")); } @@ -74,7 +76,7 @@ void trailingSpace(Class type, T expected, String string) { @ParameterizedTest(name = "{0} - {2}") @MethodSource("data") void leadingSpace(Class type, T expected, String string) { - SmallRyeConverters converters = buildConverters(); + Converters converters = ConvertersProvider.getConverters(); Converter converter = converters.getConverter(type); assertEquals(expected, converter.convert(" " + string)); } @@ -82,12 +84,8 @@ void leadingSpace(Class type, T expected, String string) { @ParameterizedTest(name = "{0} - {2}") @MethodSource("data") void leadingAndTrailingWhitespaces(Class type, T expected, String string) { - SmallRyeConverters converters = buildConverters(); + Converters converters = ConvertersProvider.getConverters(); Converter converter = converters.getConverter(type); assertEquals(expected, converter.convert(" \t " + string + "\t\t ")); } - - private static SmallRyeConverters buildConverters() { - return new SmallRyeConvertersBuilder().build(); - } } diff --git a/implementation/src/test/java/io/smallrye/converters/ConvertersTest.java b/implementation/src/test/java/io/smallrye/converters/ConvertersTest.java index 4471116..d477981 100644 --- a/implementation/src/test/java/io/smallrye/converters/ConvertersTest.java +++ b/implementation/src/test/java/io/smallrye/converters/ConvertersTest.java @@ -15,13 +15,13 @@ */ package io.smallrye.converters; -import static io.smallrye.converters.Converters.getImplicitConverter; -import static io.smallrye.converters.Converters.maximumValueConverter; -import static io.smallrye.converters.Converters.minimumValueConverter; -import static io.smallrye.converters.Converters.newArrayConverter; -import static io.smallrye.converters.Converters.newCollectionConverter; -import static io.smallrye.converters.Converters.newEmptyValueConverter; -import static io.smallrye.converters.Converters.newOptionalConverter; +import static io.smallrye.converters.ConvertersUtils.getImplicitConverter; +import static io.smallrye.converters.ConvertersUtils.maximumValueConverter; +import static io.smallrye.converters.ConvertersUtils.minimumValueConverter; +import static io.smallrye.converters.ConvertersUtils.newArrayConverter; +import static io.smallrye.converters.ConvertersUtils.newCollectionConverter; +import static io.smallrye.converters.ConvertersUtils.newEmptyValueConverter; +import static io.smallrye.converters.ConvertersUtils.newOptionalConverter; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -196,25 +196,25 @@ void maximumValue() { void empty() { SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); - assertTrue(converters.convertValue("1234", OptionalInt.class).isPresent()); - assertFalse(converters.convertValue("", OptionalInt.class).isPresent()); - assertThrows(NullPointerException.class, () -> converters.convertValue(null, OptionalInt.class)); + assertTrue(converters.convert("1234", OptionalInt.class).isPresent()); + assertFalse(converters.convert("", OptionalInt.class).isPresent()); + assertThrows(NullPointerException.class, () -> converters.convert(null, OptionalInt.class)); } @Test void shortValue() { SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); short expected = 2; - assertEquals(expected, (short) converters.convertValue("2", Short.class), "Unexpected value for short config"); - assertEquals(expected, (short) converters.convertValue("2", Short.TYPE), "Unexpected value for short config"); + assertEquals(expected, (short) converters.convert("2", Short.class), "Unexpected value for short config"); + assertEquals(expected, (short) converters.convert("2", Short.TYPE), "Unexpected value for short config"); } @Test void byteValue() { SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); byte expected = 2; - assertEquals(expected, (byte) converters.convertValue("2", Byte.class), "Unexpected value for byte config"); - assertEquals(expected, (byte) converters.convertValue("2", Byte.TYPE), "Unexpected value for byte config"); + assertEquals(expected, (byte) converters.convert("2", Byte.class), "Unexpected value for byte config"); + assertEquals(expected, (byte) converters.convert("2", Byte.TYPE), "Unexpected value for byte config"); } @Test @@ -225,8 +225,8 @@ void byteArray() { String value = Base64.getEncoder().encodeToString("bytes".getBytes()); - assertEquals("Ynl0ZXM=", converters.convertValue(value, v -> v)); - assertEquals("bytes", new String(converters.convertValue(value, byte[].class))); + assertEquals("Ynl0ZXM=", converters.convert(value, v -> v)); + assertEquals("bytes", new String(converters.convert(value, byte[].class))); } @Test @@ -235,7 +235,7 @@ void currency() { Currency expected = Currency.getInstance("GBP"); assertEquals(expected.getCurrencyCode(), - converters.convertValue("GBP", Currency.class).getCurrencyCode(), + converters.convert("GBP", Currency.class).getCurrencyCode(), "Unexpected value for byte config"); } @@ -248,7 +248,7 @@ void bitSet() { expected.set(3); expected.set(5); expected.set(7); - assertEquals(expected.toString(), converters.convertValue("AA", BitSet.class).toString(), + assertEquals(expected.toString(), converters.convert("AA", BitSet.class).toString(), "Unexpected value for byte config"); } @@ -257,7 +257,7 @@ void pattern() { SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); Pattern expected = Pattern.compile("[0-9]"); - assertEquals(expected.pattern(), converters.convertValue("[0-9]", Pattern.class).pattern(), + assertEquals(expected.pattern(), converters.convert("[0-9]", Pattern.class).pattern(), "Unexpected value for pattern"); } diff --git a/implementation/src/test/java/io/smallrye/converters/CustomConverterTest.java b/implementation/src/test/java/io/smallrye/converters/CustomConverterTest.java index e7ec8b6..f5132fe 100644 --- a/implementation/src/test/java/io/smallrye/converters/CustomConverterTest.java +++ b/implementation/src/test/java/io/smallrye/converters/CustomConverterTest.java @@ -19,7 +19,7 @@ class CustomConverterTest { @Test void customInetAddressConverter() { - SmallRyeConverters converters = buildConverters(); + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); InetAddress inetaddress = converters.getConverter(InetAddress.class).convert("10.0.0.1"); assertNotNull(inetaddress); assertArrayEquals(new byte[] { 10, 0, 0, 1 }, inetaddress.getAddress()); @@ -27,30 +27,26 @@ void customInetAddressConverter() { @Test void characterConverter() { - SmallRyeConverters converters = buildConverters(); - assertEquals('a', converters.convertValue("a", Character.class)); + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); + assertEquals('a', converters.convert("a", Character.class)); } @Test void explicitConverter() { - SmallRyeConverters converters = buildConverters(); - final Converter customConverter = new Converter() { - public Integer convert(final String value) { - return Integer.parseInt(value) * 2; - } - }; + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); + Converter customConverter = value -> Integer.parseInt(value) * 2; - assertEquals(1234, converters.convertValue("1234", Integer.class).intValue()); - assertEquals(2468, converters.convertValue("1234", customConverter).intValue()); + assertEquals(1234, converters.convert("1234", Integer.class).intValue()); + assertEquals(2468, converters.convert("1234", customConverter).intValue()); - assertEquals(singletonList(1234), converters.convertValues("1234", Integer.class, ArrayList::new)); - assertEquals(singletonList(2468), converters.convertValues("1234", customConverter, ArrayList::new)); + assertEquals(singletonList(1234), converters.convert("1234", Integer.class, ArrayList::new)); + assertEquals(singletonList(2468), converters.convert("1234", customConverter, ArrayList::new)); - assertThrows(NullPointerException.class, () -> converters.convertValue(null, Integer.class)); + assertThrows(NullPointerException.class, () -> converters.convert(null, Integer.class)); // TODO - Should this also throw NPE? For collection it does, so it is inconsistent //assertThrows(NullPointerException.class, () -> converters.convertValue(null, customConverter)); - assertThrows(NullPointerException.class, () -> converters.convertValues(null, Integer.class, ArrayList::new)); - assertThrows(NullPointerException.class, () -> converters.convertValues(null, customConverter, ArrayList::new)); + assertThrows(NullPointerException.class, () -> converters.convert(null, Integer.class, ArrayList::new)); + assertThrows(NullPointerException.class, () -> converters.convert(null, customConverter, ArrayList::new)); } @Test @@ -59,26 +55,22 @@ void UUID() { String secondUuidStringTruth = "c2d88ee5-e981-4de2-ac54-8b887cc2acbc"; UUID uuidUUIDTruth = UUID.fromString(uuidStringTruth); UUID secondUuidUUIDTruth = UUID.fromString(secondUuidStringTruth); - SmallRyeConverters converters = buildConverters(); + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); - assertEquals(uuidUUIDTruth, converters.convertValue(uuidStringTruth, UUID.class)); + assertEquals(uuidUUIDTruth, converters.convert(uuidStringTruth, UUID.class)); - assertThrows(NullPointerException.class, () -> converters.convertValue(null, UUID.class)); + assertThrows(NullPointerException.class, () -> converters.convert(null, UUID.class)); // TODO - Check this one //assertNull(converters.convertValue(" ", UUID.class)); - assertEquals(uuidUUIDTruth, converters.convertValue(uuidStringTruth.toUpperCase(Locale.ROOT), UUID.class)); + assertEquals(uuidUUIDTruth, converters.convert(uuidStringTruth.toUpperCase(Locale.ROOT), UUID.class)); - ArrayList values = converters.convertValues(uuidStringTruth + "," + secondUuidStringTruth, UUID.class, + ArrayList values = converters.convert(uuidStringTruth + "," + secondUuidStringTruth, UUID.class, ArrayList::new); assertEquals(uuidUUIDTruth, values.get(0)); assertEquals(secondUuidUUIDTruth, values.get(1)); - assertThrows(IllegalArgumentException.class, () -> converters.convertValue("invalid", UUID.class), + assertThrows(IllegalArgumentException.class, () -> converters.convert("invalid", UUID.class), "Malformed UUID should throw exception"); } - - private static SmallRyeConverters buildConverters() { - return new SmallRyeConvertersBuilder().build(); - } } diff --git a/implementation/src/test/java/io/smallrye/converters/ImplicitConverterTest.java b/implementation/src/test/java/io/smallrye/converters/ImplicitConverterTest.java index 91ab0f8..039d658 100644 --- a/implementation/src/test/java/io/smallrye/converters/ImplicitConverterTest.java +++ b/implementation/src/test/java/io/smallrye/converters/ImplicitConverterTest.java @@ -19,8 +19,8 @@ class ImplicitConverterTest { @Test - public void implicitURLConverter() { - final SmallRyeConverters converters = buildConverters(); + void implicitURLConverter() { + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); URL url = converters.getConverter(URL.class).convert("https://github.com/smallrye/smallrye-config/"); assertNotNull(url); assertEquals("https", url.getProtocol()); @@ -29,8 +29,8 @@ public void implicitURLConverter() { } @Test - public void implicitLocalDateConverter() { - SmallRyeConverters converters = buildConverters(); + void implicitLocalDateConverter() { + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); LocalDate date = converters.getConverter(LocalDate.class).convert("2019-04-01"); assertNotNull(date); assertEquals(2019, date.getYear()); @@ -61,7 +61,4 @@ void serializationOfConstructorConverter() { "Converted values to have same file path"); } - private static SmallRyeConverters buildConverters() { - return new SmallRyeConvertersBuilder().build(); - } } diff --git a/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersBuilderTest.java b/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersBuilderTest.java index 2c69c8b..c62818b 100644 --- a/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersBuilderTest.java +++ b/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersBuilderTest.java @@ -11,17 +11,17 @@ class SmallRyeConvertersBuilderTest { @Test - public void withConverters() { - final SmallRyeConverters converters = new SmallRyeConvertersBuilder() + void withConverters() { + SmallRyeConverters converters = new SmallRyeConvertersBuilder() .withConverters(new Converter[] { new DummyConverter() }).build(); - final Converter converter = converters.getConverter(String.class); + Converter converter = converters.getConverter(String.class); assertNotNull(converter); assertEquals("dummy", converter.convert("")); } @Priority(1000) - public static class DummyConverter implements Converter { + static class DummyConverter implements Converter { @Override public String convert(final String value) { return "dummy"; diff --git a/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersTest.java b/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersTest.java index 86dc02d..e5c0856 100644 --- a/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersTest.java +++ b/implementation/src/test/java/io/smallrye/converters/SmallRyeConvertersTest.java @@ -3,18 +3,135 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.bind.JsonbBuilder; + +import org.eclipse.yasson.YassonJsonb; import org.junit.jupiter.api.Test; -import io.smallrye.converters.api.Converter; +import io.smallrye.converters.api.ConvertedType; +import io.smallrye.converters.api.InputConverter; +import io.smallrye.converters.api.OutputConverter; class SmallRyeConvertersTest { @Test - public void api() { - final SmallRyeConverters converters = new SmallRyeConverters(); + void api() { + SmallRyeConverters converters = new SmallRyeConvertersBuilder().build(); assertNotNull(converters.getConverter(Integer.class)); assertNotNull(converters.getOptionalConverter(Integer.class)); - assertEquals(1, converters.convertValue("1", Integer.class).intValue()); - assertEquals("dummy", converters.convertValue("you", (Converter) value -> "dummy")); + assertEquals(1, converters.convert("1", Integer.class).intValue()); + assertEquals("dummy", converters.convert("you", value -> "dummy")); + } + + @Test + void convertedType() { + SmallRyeConverters converters = new SmallRyeConvertersBuilder() + .withInputConverter(String.class, new JsonObjectInputConverter()) + .withOutputConverter(Address.class, new AddressOutputConverter()) + .build(); + JsonObject jsonObject = Json.createObjectBuilder() + .add("street", "abc def") + .add("code", 1234) + .add("country", "ABC") + .build(); + + ConvertedType fromString = converters.from("jsonObject"); + Address address = fromString.getAs(Address.class); + assertEquals("abc def", address.getStreet()); + assertEquals(1234, address.getCode().intValue()); + assertEquals("ABC", address.getCountry()); + + ConvertedType fromJsonObject = converters.from(jsonObject); + address = fromJsonObject.getAs(Address.class); + assertEquals("abc def", address.getStreet()); + assertEquals(1234, address.getCode().intValue()); + assertEquals("ABC", address.getCountry()); + } + + @Test + void inputPassthrough() { + SmallRyeConverters converters = new SmallRyeConvertersBuilder() + .withOutputConverter(Address.class, new AddressOutputConverter()) + .build(); + JsonObject jsonObject = Json.createObjectBuilder() + .add("street", "abc def") + .add("code", 1234) + .add("country", "ABC") + .build(); + + ConvertedType fromJsonObject = converters.from(jsonObject); + Address address = fromJsonObject.getAs(Address.class); + assertEquals("abc def", address.getStreet()); + assertEquals(1234, address.getCode().intValue()); + assertEquals("ABC", address.getCountry()); + } + + static class JsonObjectInputConverter implements InputConverter { + @Override + public JsonObject convert(final String value) { + return Json.createObjectBuilder() + .add("street", "abc def") + .add("code", 1234) + .add("country", "ABC") + .build(); + } + } + + static class AddressOutputConverter implements OutputConverter { + @Override + public Address convert(final ConvertedType value) { + YassonJsonb jsonb = (YassonJsonb) JsonbBuilder.create(); + return jsonb.fromJsonStructure(value.getRaw(), Address.class); + } + } + + public static class Address { + private String street; + private Integer code; + private String country; + + public Address() { + } + + public Address(final String street, final Integer code, final String country) { + this.street = street; + this.code = code; + this.country = country; + } + + public String getStreet() { + return street; + } + + public void setStreet(final String street) { + this.street = street; + } + + public Integer getCode() { + return code; + } + + public void setCode(final Integer code) { + this.code = code; + } + + public String getCountry() { + return country; + } + + public void setCountry(final String country) { + this.country = country; + } + + @Override + public String toString() { + return "Address{" + + "street='" + street + '\'' + + ", code=" + code + + ", country='" + country + '\'' + + '}'; + } } }