Skip to content
This repository was archived by the owner on Jan 20, 2023. It is now read-only.

Commit 48b9bd4

Browse files
authored
Merge pull request #4 from k163377/feature
Add class deserializer support.
2 parents a7c872b + 3422fc5 commit 48b9bd4

File tree

9 files changed

+145
-80
lines changed

9 files changed

+145
-80
lines changed

.idea/KMapper.iml

Lines changed: 0 additions & 2 deletions
This file was deleted.

.idea/modules/KMapper.main.iml

Lines changed: 0 additions & 21 deletions
This file was deleted.

.idea/modules/KMapper.test.iml

Lines changed: 0 additions & 38 deletions
This file was deleted.

build.gradle.kts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group = "com.mapk"
9-
version = "0.2"
9+
version = "0.3"
1010

1111
java {
1212
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -30,8 +30,8 @@ repositories {
3030
dependencies {
3131
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
3232
implementation(kotlin("reflect"))
33-
// 使うのはRowMapperのみなため、他はexclude
34-
implementation(group = "org.springframework", name = "spring-jdbc", version = "5.2.4.RELEASE") {
33+
// 使うのはRowMapperのみなため他はexclude、またバージョンそのものは使う相手に合わせるためcompileOnly
34+
compileOnly(group = "org.springframework", name = "spring-jdbc", version = "5.2.4.RELEASE") {
3535
exclude(module = "spring-beans")
3636
exclude(module = "spring-jcl")
3737
exclude(module = "spring-tx")
@@ -47,6 +47,8 @@ dependencies {
4747
// 現状プロパティ名の変換はテストでしか使っていないのでtestImplementation
4848
// https://mvnrepository.com/artifact/com.google.guava/guava
4949
testImplementation(group = "com.google.guava", name = "guava", version = "28.2-jre")
50+
// テスト時には無いと困るため、別口でimplementation
51+
testImplementation(group = "org.springframework", name = "spring-jdbc", version = "5.2.4.RELEASE")
5052
}
5153

5254
tasks {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.mapk.annotations
2+
3+
@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION)
4+
@Retention(AnnotationRetention.RUNTIME)
5+
@MustBeDocumented
6+
annotation class KColumnDeserializer

src/main/kotlin/com/mapk/krowmapper/KRowMapper.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.mapk.krowmapper
22

3-
import com.mapk.core.EnumMapper
43
import com.mapk.core.KFunctionForCall
54
import com.mapk.core.isUseDefaultArgument
65
import com.mapk.core.toKConstructor
@@ -22,24 +21,17 @@ class KRowMapper<T : Any> private constructor(
2221
clazz.toKConstructor(), propertyNameConverter
2322
)
2423

25-
private val parameters: List<ParameterForMap<*>> = function.parameters
24+
private val parameters: List<ParameterForMap> = function.parameters
2625
.filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() }
2726
.map { ParameterForMap.newInstance(it, propertyNameConverter) }
2827

2928
override fun mapRow(rs: ResultSet, rowNum: Int): T {
3029
val argumentBucket = function.getArgumentBucket()
3130

3231
parameters.forEach { param ->
33-
argumentBucket.putIfAbsent(param.param, when {
34-
param.clazz.isEnum -> EnumMapper.getEnum(param.clazz, rs.getObject(param.name, stringClazz))
35-
else -> rs.getObject(param.name, param.clazz)
36-
})
32+
argumentBucket.putIfAbsent(param.param, param.getObject(rs))
3733
}
3834

3935
return function.call(argumentBucket)
4036
}
41-
42-
companion object {
43-
private val stringClazz = String::class.java
44-
}
4537
}
Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,87 @@
11
package com.mapk.krowmapper
22

3+
import com.mapk.annotations.KColumnDeserializer
4+
import com.mapk.core.EnumMapper
5+
import com.mapk.core.KFunctionWithInstance
36
import com.mapk.core.getAliasOrName
7+
import java.lang.IllegalArgumentException
8+
import java.sql.ResultSet
49
import kotlin.reflect.KClass
10+
import kotlin.reflect.KFunction
511
import kotlin.reflect.KParameter
12+
import kotlin.reflect.full.companionObjectInstance
13+
import kotlin.reflect.full.functions
14+
import kotlin.reflect.full.staticFunctions
15+
import kotlin.reflect.jvm.isAccessible
16+
import kotlin.reflect.jvm.jvmName
617

7-
class ParameterForMap<D : Any> private constructor(
8-
val name: String,
18+
class ParameterForMap private constructor(
919
val param: KParameter,
10-
val clazz: Class<D>
20+
private val name: String,
21+
kClazz: KClass<*>
1122
) {
23+
private val javaClazz: Class<*> = kClazz.java
24+
private val deserializer: KFunction<*>?
25+
26+
init {
27+
val deserializers = deserializerFromConstructors(kClazz) +
28+
deserializerFromStaticMethods(kClazz) +
29+
deserializerFromCompanionObject(kClazz)
30+
31+
deserializer = when {
32+
deserializers.isEmpty() -> null
33+
deserializers.size == 1 -> deserializers.single()
34+
else -> throw IllegalArgumentException("Find multiple deserializer from ${kClazz.jvmName}")
35+
}
36+
}
37+
38+
fun getObject(rs: ResultSet): Any? = when {
39+
javaClazz.isEnum -> EnumMapper.getEnum(javaClazz, rs.getString(name))
40+
else -> {
41+
val value: Any? = rs.getObject(name, javaClazz)
42+
deserializer?.call(value) ?: value
43+
}
44+
}
45+
1246
companion object {
13-
fun newInstance(param: KParameter, propertyNameConverter: (String) -> String = { it }): ParameterForMap<*> {
47+
fun newInstance(param: KParameter, propertyNameConverter: (String) -> String = { it }): ParameterForMap {
1448
return ParameterForMap(
15-
propertyNameConverter(param.getAliasOrName()!!),
1649
param,
17-
(param.type.classifier as KClass<*>).java
50+
propertyNameConverter(param.getAliasOrName()!!),
51+
param.type.classifier as KClass<*>
1852
)
1953
}
2054
}
2155
}
56+
57+
private fun <T> Collection<KFunction<T>>.getDeserializerFromFunctions(): Collection<KFunction<T>> {
58+
return filter { it.annotations.any { annotation -> annotation is KColumnDeserializer } }
59+
.map { func ->
60+
func.isAccessible = true
61+
func
62+
}
63+
}
64+
65+
private fun <T : Any> deserializerFromConstructors(clazz: KClass<T>): Collection<KFunction<T>> {
66+
return clazz.constructors.getDeserializerFromFunctions()
67+
}
68+
69+
@Suppress("UNCHECKED_CAST")
70+
private fun <T : Any> deserializerFromStaticMethods(clazz: KClass<T>): Collection<KFunction<T>> {
71+
val staticFunctions: Collection<KFunction<T>> = clazz.staticFunctions as Collection<KFunction<T>>
72+
return staticFunctions.getDeserializerFromFunctions()
73+
}
74+
75+
@Suppress("UNCHECKED_CAST")
76+
private fun <T : Any> deserializerFromCompanionObject(clazz: KClass<T>): Collection<KFunction<T>> {
77+
return clazz.companionObjectInstance?.let { companionObject ->
78+
companionObject::class.functions
79+
.filter { it.annotations.any { annotation -> annotation is KColumnDeserializer } }
80+
.map { function ->
81+
KFunctionWithInstance(
82+
function,
83+
companionObject
84+
) as KFunction<T>
85+
}.toSet()
86+
} ?: emptySet()
87+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.mapk.krowmapper;
2+
3+
import com.mapk.annotations.KColumnDeserializer;
4+
5+
public class ByStaticMethod {
6+
private final String bazString;
7+
8+
public ByStaticMethod(String bazString) {
9+
this.bazString = bazString;
10+
}
11+
12+
public String getBazString() {
13+
return bazString;
14+
}
15+
16+
@KColumnDeserializer
17+
public static ByStaticMethod factory(Integer bazArg) {
18+
return new ByStaticMethod(bazArg.toString());
19+
}
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.mapk.krowmapper
2+
3+
import com.mapk.annotations.KColumnDeserializer
4+
import io.mockk.every
5+
import io.mockk.mockk
6+
import java.sql.ResultSet
7+
import org.junit.jupiter.api.Assertions
8+
import org.junit.jupiter.api.DisplayName
9+
import org.junit.jupiter.api.Test
10+
11+
class DeserializeByMethodTest {
12+
data class ByConstructor @KColumnDeserializer constructor(val fooString: String)
13+
data class ByCompanionObject(val barInt: Int) {
14+
companion object {
15+
@KColumnDeserializer
16+
fun factory(bar: String) = ByCompanionObject(bar.toInt())
17+
}
18+
}
19+
20+
data class Dst(
21+
val foo: ByConstructor,
22+
val bar: ByCompanionObject,
23+
val baz: ByStaticMethod
24+
)
25+
26+
@Test
27+
@DisplayName("マッピングテスト")
28+
fun test() {
29+
val resultSet = mockk<ResultSet>()
30+
every { resultSet.getObject("foo", any<Class<*>>()) } returns "foo"
31+
every { resultSet.getObject("bar", any<Class<*>>()) } returns "123"
32+
every { resultSet.getObject("baz", any<Class<*>>()) } returns 321
33+
34+
val result = KRowMapper(::Dst).mapRow(resultSet, 0)
35+
36+
Assertions.assertEquals("foo", result.foo.fooString)
37+
Assertions.assertEquals(123, result.bar.barInt)
38+
Assertions.assertEquals("321", result.baz.bazString)
39+
}
40+
}

0 commit comments

Comments
 (0)