From f14f6c5f78896c80c4394bc87fd5f24a8d0c5c7e Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sun, 25 Feb 2018 12:28:26 +0300 Subject: [PATCH 1/6] potential support of converters of different types of types --- .../com/github/andrewoma/kwery/mapper/Table.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt index efc2e5d..c5117ee 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt @@ -147,8 +147,18 @@ abstract class Table(val name: String, val config: TableConfigurati // TODO ... converters are currently defined as Java classes as I can't figure out how to // convert a nullable KType into its non-nullable equivalent // Try udalov's workaround: (t.javaType as Class<*>).kotlin.defaultType` - val javaClass = type.javaType as Class - val converter = config.converters[javaClass] ?: if (javaClass.isEnum) EnumByNameConverter(javaClass as Class) as Converter else null + val javaType = type.javaType + return when (javaType) { + is Class<*> -> converterForClass(type) + else -> error("Type $javaType is not supported.") + } + } + + private fun converterForClass(type: KType): Converter { + val javaClass = type.javaClass + val converter = config.converters[javaClass] + ?: if (javaClass.isEnum) EnumByNameConverter(javaClass as Class) as Converter else null + checkNotNull(converter) { "Converter undefined for type $type as $javaClass" } return (if (type.isMarkedNullable) optional(converter!! as Converter) else converter) as Converter } From 3e96b9f2d9f21dc8724d63ac9517eb824da07b49 Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sun, 25 Feb 2018 13:48:31 +0300 Subject: [PATCH 2/6] added Converter>> --- .../github/andrewoma/kwery/mapper/Table.kt | 33 ++++++++++-- .../kwery/mappertest/ConverterTest.kt | 51 +++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/ConverterTest.kt diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt index c5117ee..35cded8 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt @@ -25,12 +25,15 @@ package com.github.andrewoma.kwery.mapper import com.github.andrewoma.kommon.collection.hashMapOfExpectedSize import com.github.andrewoma.kwery.core.Row import com.github.andrewoma.kwery.core.Session +import sun.misc.SharedSecrets import java.lang.reflect.ParameterizedType +import java.sql.Connection import java.util.* import kotlin.properties.ReadOnlyProperty import kotlin.reflect.* import kotlin.reflect.full.defaultType import kotlin.reflect.jvm.javaType +import kotlin.reflect.jvm.jvmErasure /** @@ -142,7 +145,6 @@ abstract class Table(val name: String, val config: TableConfigurati // Can't cast T to Enum due to recursive type, so cast to any enum to satisfy compiler private enum class DummyEnum - @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_UNIT_OR_ANY", "CAST_NEVER_SUCCEEDS") protected fun converter(type: KType): Converter { // TODO ... converters are currently defined as Java classes as I can't figure out how to // convert a nullable KType into its non-nullable equivalent @@ -150,19 +152,44 @@ abstract class Table(val name: String, val config: TableConfigurati val javaType = type.javaType return when (javaType) { is Class<*> -> converterForClass(type) + is ParameterizedType -> converterForParameterized(type) else -> error("Type $javaType is not supported.") } } + @Suppress("UNCHECKED_CAST") private fun converterForClass(type: KType): Converter { - val javaClass = type.javaClass - val converter = config.converters[javaClass] + val javaClass = type.javaType as Class<*> + val converter = config.converters [javaClass] ?: if (javaClass.isEnum) EnumByNameConverter(javaClass as Class) as Converter else null checkNotNull(converter) { "Converter undefined for type $type as $javaClass" } return (if (type.isMarkedNullable) optional(converter!! as Converter) else converter) as Converter } + @Suppress("UNCHECKED_CAST") + private fun converterForParameterized(type: KType): Converter = when (type.jvmErasure.java) { + Set::class.java -> { + val e = (type.javaType as ParameterizedType).actualTypeArguments[0] + if (e is Class<*> && e.isEnum) enumSetConverter(e as Class) as Converter + else error("Sets of $e are not supported.") + } + else -> error("Parameterized type ${type.javaType} is not supported.") + } + + private fun > enumSetConverter(eType: Class): Converter> { + SharedSecrets.getJavaLangAccess().getEnumConstantsShared(eType).forEach { + check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } + } + val from: (Row, String) -> Set = { row, s -> + val values = row.string(s) + if (values.isEmpty()) emptySet() + else values.split('|').mapTo(EnumSet.noneOf(eType)) { java.lang.Enum.valueOf(eType, it) } + } + val to: (Connection, Set) -> Any? = { _, set -> set.joinToString("|", transform = Enum::name) } + return Converter(from, to) + } + @Suppress("UNCHECKED_CAST") protected fun default(type: KType): T { if (type.isMarkedNullable) return null as T diff --git a/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/ConverterTest.kt b/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/ConverterTest.kt new file mode 100644 index 0000000..32c67f0 --- /dev/null +++ b/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/ConverterTest.kt @@ -0,0 +1,51 @@ +package com.github.andrewoma.kwery.mappertest + +import com.github.andrewoma.kwery.core.Row +import com.github.andrewoma.kwery.mapper.Column +import com.github.andrewoma.kwery.mapper.Table +import com.github.andrewoma.kwery.mapper.Value +import org.junit.Test +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Proxy +import java.sql.Connection +import java.sql.ResultSet +import java.util.* +import kotlin.reflect.KType +import kotlin.test.assertEquals + + +class ConverterTest { + + private val ih = InvocationHandler { _, _, _ -> error("unused") } + private val enumSetProp: Set = EnumSet.noneOf(Thread.State::class.java) + + @Test + fun enumSetConverterTest() { + val converter = TestTable.testConverter>(this::enumSetProp.returnType) + val connection = Proxy.newProxyInstance(javaClass.classLoader, arrayOf(Connection::class.java), ih) as Connection + + val empty = converter.to(connection, EnumSet.noneOf(Thread.State::class.java)) + assertEquals("", empty) + assertEquals(emptySet(), converter.from(Row(StringResultSet("")), "")) + + val single = converter.to(connection, EnumSet.of(Thread.State.RUNNABLE)) + assertEquals("RUNNABLE", single) + assertEquals(setOf(Thread.State.RUNNABLE), converter.from(Row(StringResultSet("RUNNABLE")), "")) + + val several = converter.to(connection, EnumSet.of(Thread.State.RUNNABLE, Thread.State.WAITING)) + assertEquals("RUNNABLE|WAITING", several) + assertEquals(setOf(Thread.State.RUNNABLE, Thread.State.WAITING), converter.from(Row(StringResultSet("RUNNABLE|WAITING")), "")) + } + + private object TestTable : Table("unused") { + override fun idColumns(id: Nothing?): Set, *>> = error("unused") + override fun create(value: Value): Any = error("unused") + fun testConverter(type: KType) = converter(type) + } + + private inner class StringResultSet(private val value: String) : + ResultSet by Proxy.newProxyInstance(StringResultSet::class.java.classLoader, arrayOf(ResultSet::class.java), ih) as ResultSet { + override fun getString(columnLabel: String?): String = value + } + +} From 01471a297917135645e0980e3cedbcc86ecec84f Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sun, 25 Feb 2018 13:58:48 +0300 Subject: [PATCH 3/6] moved Converter>> to Converters, not using SharedSecrets which is inaccessible in JDK 9 --- .../andrewoma/kwery/mapper/Converters.kt | 14 ++++++++++++ .../github/andrewoma/kwery/mapper/Table.kt | 22 +++++-------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt index c7aae6c..2744885 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt @@ -28,6 +28,7 @@ import java.sql.Connection import java.sql.Date import java.sql.Time import java.sql.Timestamp +import java.util.* open class Converter( val from: (Row, String) -> R, @@ -116,3 +117,16 @@ class EnumByNameConverter>(type: Class) : SimpleConverter( { row, c -> java.lang.Enum.valueOf(type, row.string(c)) }, { it.name } ) + +fun > EnumSetConverter(eType: Class): Converter> { + eType.enumConstants.forEach { + check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } + } + val from: (Row, String) -> Set = { row, s -> + val values = row.string(s) + if (values.isEmpty()) emptySet() + else values.split('|').mapTo(EnumSet.noneOf(eType)) { java.lang.Enum.valueOf(eType, it) } + } + val to: (Connection, Set) -> Any? = { _, set -> set.joinToString("|", transform = Enum::name) } + return Converter(from, to) +} diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt index 35cded8..14426b4 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt @@ -25,12 +25,13 @@ package com.github.andrewoma.kwery.mapper import com.github.andrewoma.kommon.collection.hashMapOfExpectedSize import com.github.andrewoma.kwery.core.Row import com.github.andrewoma.kwery.core.Session -import sun.misc.SharedSecrets import java.lang.reflect.ParameterizedType -import java.sql.Connection import java.util.* import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.* +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.KType import kotlin.reflect.full.defaultType import kotlin.reflect.jvm.javaType import kotlin.reflect.jvm.jvmErasure @@ -171,25 +172,12 @@ abstract class Table(val name: String, val config: TableConfigurati private fun converterForParameterized(type: KType): Converter = when (type.jvmErasure.java) { Set::class.java -> { val e = (type.javaType as ParameterizedType).actualTypeArguments[0] - if (e is Class<*> && e.isEnum) enumSetConverter(e as Class) as Converter + if (e is Class<*> && e.isEnum) EnumSetConverter(e as Class) as Converter else error("Sets of $e are not supported.") } else -> error("Parameterized type ${type.javaType} is not supported.") } - private fun > enumSetConverter(eType: Class): Converter> { - SharedSecrets.getJavaLangAccess().getEnumConstantsShared(eType).forEach { - check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } - } - val from: (Row, String) -> Set = { row, s -> - val values = row.string(s) - if (values.isEmpty()) emptySet() - else values.split('|').mapTo(EnumSet.noneOf(eType)) { java.lang.Enum.valueOf(eType, it) } - } - val to: (Connection, Set) -> Any? = { _, set -> set.joinToString("|", transform = Enum::name) } - return Converter(from, to) - } - @Suppress("UNCHECKED_CAST") protected fun default(type: KType): T { if (type.isMarkedNullable) return null as T From 67bc989500a36f4a54189fcba8170a41acadafc1 Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sun, 25 Feb 2018 14:06:42 +0300 Subject: [PATCH 4/6] wrapped Converter>> into its own class with exposed type, which if useful for CRUD wrappers around kwery-mapper --- .../andrewoma/kwery/mapper/Converters.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt index 2744885..6fd6c91 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt @@ -118,15 +118,22 @@ class EnumByNameConverter>(type: Class) : SimpleConverter( { it.name } ) -fun > EnumSetConverter(eType: Class): Converter> { - eType.enumConstants.forEach { - check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } +class EnumSetConverter>( + // exposing type intentionally, theoretically `type` may become public property of Converter + val eType: Class +): Converter>( + { row, s -> + val values = row.string(s) + if (values.isEmpty()) emptySet() + else values.split('|').mapTo(EnumSet.noneOf(eType)) { java.lang.Enum.valueOf(eType, it) } + }, + { _, set -> set.joinToString("|", transform = Enum::name) } +) { + + init { + eType.enumConstants.forEach { + check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } + } } - val from: (Row, String) -> Set = { row, s -> - val values = row.string(s) - if (values.isEmpty()) emptySet() - else values.split('|').mapTo(EnumSet.noneOf(eType)) { java.lang.Enum.valueOf(eType, it) } - } - val to: (Connection, Set) -> Any? = { _, set -> set.joinToString("|", transform = Enum::name) } - return Converter(from, to) + } From 3ace16ddcc1a7309069012bcc60ad18ac1655a88 Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sun, 25 Feb 2018 15:54:04 +0300 Subject: [PATCH 5/6] added `type` property to Set> converter --- .../main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt index 6fd6c91..97fd1de 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt @@ -130,6 +130,8 @@ class EnumSetConverter>( { _, set -> set.joinToString("|", transform = Enum::name) } ) { + val type get() = Set::class.java + init { eType.enumConstants.forEach { check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } From 548ce43e2196557eed4243427e8e8614c1442b2d Mon Sep 17 00:00:00 2001 From: Miha_x64 Date: Sun, 25 Feb 2018 16:08:16 +0300 Subject: [PATCH 6/6] added `kType` and `type` properties to Set> converter --- .../github/andrewoma/kwery/mapper/Converters.kt | 14 ++++++++++++-- .../com/github/andrewoma/kwery/mapper/Table.kt | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt index 97fd1de..c9c86db 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Converters.kt @@ -23,12 +23,15 @@ package com.github.andrewoma.kwery.mapper import com.github.andrewoma.kwery.core.Row +import java.lang.reflect.ParameterizedType import java.math.BigDecimal import java.sql.Connection import java.sql.Date import java.sql.Time import java.sql.Timestamp import java.util.* +import kotlin.reflect.KType +import kotlin.reflect.jvm.javaType open class Converter( val from: (Row, String) -> R, @@ -119,7 +122,8 @@ class EnumByNameConverter>(type: Class) : SimpleConverter( ) class EnumSetConverter>( - // exposing type intentionally, theoretically `type` may become public property of Converter + // exposing types intentionally, theoretically `type` may become public property of Converter + val kType: KType, val eType: Class ): Converter>( { row, s -> @@ -130,9 +134,15 @@ class EnumSetConverter>( { _, set -> set.joinToString("|", transform = Enum::name) } ) { - val type get() = Set::class.java + val type = kType.javaType init { + check(type === kType.javaType) + check(type is ParameterizedType) + type as ParameterizedType + check(type.rawType === Set::class.java) + check(type.actualTypeArguments[0] is Class<*>) + check(Enum::class.java.isAssignableFrom(type.actualTypeArguments[0] as Class<*>)) eType.enumConstants.forEach { check(!it.name.contains('|')) { "Hope this won't happen. Enum constant name contains |." } } diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt index 14426b4..6a48d22 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt @@ -172,7 +172,7 @@ abstract class Table(val name: String, val config: TableConfigurati private fun converterForParameterized(type: KType): Converter = when (type.jvmErasure.java) { Set::class.java -> { val e = (type.javaType as ParameterizedType).actualTypeArguments[0] - if (e is Class<*> && e.isEnum) EnumSetConverter(e as Class) as Converter + if (e is Class<*> && e.isEnum) EnumSetConverter(type, e as Class) as Converter else error("Sets of $e are not supported.") } else -> error("Parameterized type ${type.javaType} is not supported.")