diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..61a9130
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index bc8d0a3..38167d7 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,7 @@
-
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 6073570..166966d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,12 +1,12 @@
plugins {
id("maven")
id("java")
- id("org.jetbrains.kotlin.jvm") version "1.3.71"
+ id("org.jetbrains.kotlin.jvm") version "1.3.72"
id("org.jlleitschuh.gradle.ktlint") version "9.2.1"
}
group = "com.mapk"
-version = "0.22"
+version = "0.23"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -18,7 +18,7 @@ buildscript {
}
dependencies {
- classpath(kotlin("gradle-plugin", version = "1.3.71"))
+ classpath(kotlin("gradle-plugin"))
}
}
@@ -30,10 +30,10 @@ repositories {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(kotlin("reflect"))
- api("com.github.ProjectMapK:Shared:0.10")
+ api("com.github.ProjectMapK:Shared:0.11")
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
- testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.1") {
+ testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.2") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
// 現状プロパティ名の変換はテストでしか使っていないのでtestImplementation
diff --git a/src/main/kotlin/com/mapk/kmapper/BoundKMapper.kt b/src/main/kotlin/com/mapk/kmapper/BoundKMapper.kt
index d487b30..d9b6e7e 100644
--- a/src/main/kotlin/com/mapk/kmapper/BoundKMapper.kt
+++ b/src/main/kotlin/com/mapk/kmapper/BoundKMapper.kt
@@ -20,7 +20,7 @@ import kotlin.reflect.jvm.jvmName
class BoundKMapper private constructor(
private val function: KFunctionForCall,
src: KClass,
- parameterNameConverter: (String) -> String = { it }
+ parameterNameConverter: (String) -> String
) {
constructor(function: KFunction, src: KClass, parameterNameConverter: (String) -> String = { it }) : this(
KFunctionForCall(function), src, parameterNameConverter
@@ -45,7 +45,7 @@ class BoundKMapper private constructor(
.filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() }
.mapNotNull {
val temp = srcPropertiesMap[parameterNameConverter(it.getAliasOrName()!!)]?.let { property ->
- BoundParameterForMap(it, property)
+ BoundParameterForMap.newInstance(it, property)
}
// 必須引数に対応するプロパティがsrcに定義されていない場合エラー
diff --git a/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt
index 03d0d96..0f954ab 100644
--- a/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt
+++ b/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt
@@ -2,41 +2,83 @@ package com.mapk.kmapper
import com.mapk.core.EnumMapper
import java.lang.IllegalArgumentException
+import java.lang.reflect.Method
import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.javaGetter
@Suppress("UNCHECKED_CAST")
-internal class BoundParameterForMap(val param: KParameter, property: KProperty1) {
- val map: (S) -> Any?
+internal sealed class BoundParameterForMap {
+ abstract val param: KParameter
+ protected abstract val propertyGetter: Method
- init {
- // ゲッターが無いならエラー
- val propertyGetter = property.javaGetter
- ?: throw IllegalArgumentException("${property.name} does not have getter.")
- propertyGetter.isAccessible = true
+ abstract fun map(src: S): Any?
- val paramClazz = param.type.classifier as KClass<*>
- val propertyClazz = property.returnType.classifier as KClass<*>
+ private class Plain(
+ override val param: KParameter,
+ override val propertyGetter: Method
+ ) : BoundParameterForMap() {
+ override fun map(src: S): Any? = propertyGetter.invoke(src)
+ }
+
+ private class UseConverter(
+ override val param: KParameter,
+ override val propertyGetter: Method,
+ private val converter: KFunction<*>
+ ) : BoundParameterForMap() {
+ override fun map(src: S): Any? = converter.call(propertyGetter.invoke(src))
+ }
+
+ private class ToEnum(
+ override val param: KParameter,
+ override val propertyGetter: Method,
+ private val paramClazz: Class<*>
+ ) : BoundParameterForMap() {
+ override fun map(src: S): Any? = EnumMapper.getEnum(paramClazz, propertyGetter.invoke(src) as String)
+ }
+
+ private class ToString(
+ override val param: KParameter,
+ override val propertyGetter: Method
+ ) : BoundParameterForMap() {
+ override fun map(src: S): String? = propertyGetter.invoke(src).toString()
+ }
+
+ companion object {
+ fun newInstance(param: KParameter, property: KProperty1): BoundParameterForMap {
+ // ゲッターが無いならエラー
+ val propertyGetter = property.javaGetter
+ ?: throw IllegalArgumentException("${property.name} does not have getter.")
+ propertyGetter.isAccessible = true
+
+ val paramClazz = param.type.classifier as KClass<*>
+ val propertyClazz = property.returnType.classifier as KClass<*>
- val converter = paramClazz.getConverters()
- .filter { (key, _) -> propertyClazz.isSubclassOf(key) }
- .let {
- if (1 < it.size) throw IllegalArgumentException("${param.name} has multiple converter. $it")
+ // コンバータが取れた場合
+ paramClazz.getConverters()
+ .filter { (key, _) -> propertyClazz.isSubclassOf(key) }
+ .let {
+ if (1 < it.size) throw IllegalArgumentException("${param.name} has multiple converter. $it")
- it.singleOrNull()?.second
+ it.singleOrNull()?.second
+ }?.let {
+ return UseConverter(param, propertyGetter, it)
+ }
+
+ if (paramClazz.isSubclassOf(propertyClazz)) {
+ return Plain(param, propertyGetter)
}
- map = when {
- converter != null -> { { converter.call(propertyGetter.invoke(it)) } }
- paramClazz.isSubclassOf(propertyClazz) -> { { propertyGetter.invoke(it) } }
- paramClazz.java.isEnum && propertyClazz == String::class -> { {
- EnumMapper.getEnum(paramClazz.java, propertyGetter.invoke(it) as String)
- } }
- paramClazz == String::class -> { { propertyGetter.invoke(it).toString() } }
- else -> throw IllegalArgumentException("Can not convert $propertyClazz to $paramClazz")
+ val javaClazz = paramClazz.java
+
+ return when {
+ javaClazz.isEnum && propertyClazz == String::class -> ToEnum(param, propertyGetter, javaClazz)
+ paramClazz == String::class -> ToString(param, propertyGetter)
+ else -> throw IllegalArgumentException("Can not convert $propertyClazz to $paramClazz")
+ }
}
}
}
diff --git a/src/main/kotlin/com/mapk/kmapper/KMapper.kt b/src/main/kotlin/com/mapk/kmapper/KMapper.kt
index 910cead..4faff9a 100644
--- a/src/main/kotlin/com/mapk/kmapper/KMapper.kt
+++ b/src/main/kotlin/com/mapk/kmapper/KMapper.kt
@@ -33,7 +33,7 @@ class KMapper private constructor(
.filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() }
.associate { (parameterNameConverter(it.getAliasOrName()!!)) to ParameterForMap.newInstance(it) }
- private val getCache: ConcurrentMap, List<(Any, ArgumentBucket) -> Unit>> = ConcurrentHashMap()
+ private val getCache: ConcurrentMap, List> = ConcurrentHashMap()
private fun bindArguments(argumentBucket: ArgumentBucket, src: Any) {
val clazz = src::class
@@ -41,11 +41,11 @@ class KMapper private constructor(
// キャッシュヒットしたら登録した内容に沿って取得処理を行う
getCache[clazz]?.let { getters ->
// 取得対象フィールドは十分絞り込んでいると考えられるため、終了判定は行わない
- getters.forEach { it(src, argumentBucket) }
+ getters.forEach { it.bindArgument(src, argumentBucket) }
return
}
- val tempCacheArrayList = ArrayList<(Any, ArgumentBucket) -> Unit>()
+ val tempBinderArrayList = ArrayList()
src::class.memberProperties.forEach outer@{ property ->
// propertyが公開されていない場合は処理を行わない
@@ -64,19 +64,14 @@ class KMapper private constructor(
parameterMap[alias ?: property.name]?.let { param ->
javaGetter.isAccessible = true
- val tempCache = { value: Any, bucket: ArgumentBucket ->
- // 初期化済みであれば高コストな取得処理は行わない
- if (!bucket.containsKey(param.param)) {
- // javaGetterを呼び出す方が高速
- bucket.putIfAbsent(param.param, javaGetter.invoke(value)?.let { param.mapObject(it) })
- }
- }
- tempCache(src, argumentBucket)
- tempCacheArrayList.add(tempCache)
+ val binder = ArgumentBinder(param, javaGetter)
+
+ binder.bindArgument(src, argumentBucket)
+ tempBinderArrayList.add(binder)
// キャッシュの整合性を保つため、ここでは終了判定を行わない
}
}
- getCache.putIfAbsent(clazz, tempCacheArrayList)
+ getCache.putIfAbsent(clazz, tempBinderArrayList)
}
private fun bindArguments(argumentBucket: ArgumentBucket, src: Map<*, *>) {
@@ -131,3 +126,13 @@ class KMapper private constructor(
return function.call(bucket)
}
}
+
+private class ArgumentBinder(private val param: ParameterForMap<*>, private val javaGetter: Method) {
+ fun bindArgument(value: Any, bucket: ArgumentBucket) {
+ // 初期化済みであれば高コストな取得処理は行わない
+ if (!bucket.containsKey(param.param)) {
+ // javaGetterを呼び出す方が高速
+ bucket.putIfAbsent(param.param, javaGetter.invoke(value)?.let { param.mapObject(it) })
+ }
+ }
+}
diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt
index 25d1875..eceec68 100644
--- a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt
+++ b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt
@@ -15,33 +15,33 @@ internal class ParameterForMap private constructor(val param: KParamete
// リストの長さが小さいと期待されるためこの形で実装しているが、理想的にはmap的なものが使いたい
private val converters: Set, KFunction>> = clazz.getConverters()
- private val convertCache: ConcurrentMap, (Any) -> Any?> = ConcurrentHashMap()
+ private val convertCache: ConcurrentMap, ParameterProcessor> = ConcurrentHashMap()
fun mapObject(value: U): Any? {
val valueClazz: KClass<*> = value::class
// 取得方法のキャッシュが有ればそれを用いる
- convertCache[valueClazz]?.let { return it(value) }
+ convertCache[valueClazz]?.let { return it.process(value) }
// パラメータに対してvalueが代入可能(同じもしくは親クラス)であればそのまま用いる
if (clazz.isSuperclassOf(valueClazz)) {
- convertCache.putIfAbsent(valueClazz) { it }
+ convertCache.putIfAbsent(valueClazz, ParameterProcessor.Plain)
return value
}
val converter: KFunction<*>? = converters.getConverter(valueClazz)
- val lambda: (Any) -> Any? = when {
+ val processor: ParameterProcessor = when {
// converterに一致する組み合わせが有れば設定されていればそれを使う
- converter != null -> { { converter.call(it) } }
+ converter != null -> ParameterProcessor.UseConverter(converter)
// 要求された値がenumかつ元が文字列ならenum mapperでマップ
- javaClazz.isEnum && value is String -> { { EnumMapper.getEnum(javaClazz, it as String) } }
+ javaClazz.isEnum && value is String -> ParameterProcessor.ToEnum(javaClazz)
// 要求されているパラメータがStringならtoStringする
- clazz == String::class -> { { it.toString() } }
+ clazz == String::class -> ParameterProcessor.ToString
else -> throw IllegalArgumentException("Can not convert $valueClazz to $clazz")
}
- convertCache.putIfAbsent(valueClazz, lambda)
- return lambda(value)
+ convertCache.putIfAbsent(valueClazz, processor)
+ return processor.process(value)
}
companion object {
@@ -50,3 +50,23 @@ internal class ParameterForMap private constructor(val param: KParamete
}
}
}
+
+private sealed class ParameterProcessor {
+ abstract fun process(value: Any): Any?
+
+ object Plain : ParameterProcessor() {
+ override fun process(value: Any): Any? = value
+ }
+
+ class UseConverter(private val converter: KFunction<*>) : ParameterProcessor() {
+ override fun process(value: Any): Any? = converter.call(value)
+ }
+
+ class ToEnum(private val javaClazz: Class<*>) : ParameterProcessor() {
+ override fun process(value: Any): Any? = EnumMapper.getEnum(javaClazz, value as String)
+ }
+
+ object ToString : ParameterProcessor() {
+ override fun process(value: Any): Any? = value.toString()
+ }
+}