From 2fe28d0e05568844a244b85711b12394be971c79 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 11 Apr 2020 04:23:28 +0900 Subject: [PATCH 01/12] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=A2=E3=83=83=E3=83=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7d2c579..2ec2853 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "com.mapk" -version = "0.20" +version = "0.21" java { sourceCompatibility = JavaVersion.VERSION_1_8 From 92d29baa9d23b036c0f52d753dafe6e40bfc17b3 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 11 Apr 2020 22:54:38 +0900 Subject: [PATCH 02/12] =?UTF-8?q?PlainKMapper=E3=81=8B=E3=82=89=E3=82=B3?= =?UTF-8?q?=E3=83=94=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/mapk/kmapper/KMapper.kt | 111 ++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/main/kotlin/com/mapk/kmapper/KMapper.kt diff --git a/src/main/kotlin/com/mapk/kmapper/KMapper.kt b/src/main/kotlin/com/mapk/kmapper/KMapper.kt new file mode 100644 index 0000000..f51f76a --- /dev/null +++ b/src/main/kotlin/com/mapk/kmapper/KMapper.kt @@ -0,0 +1,111 @@ +package com.mapk.kmapper + +import com.mapk.annotations.KGetterAlias +import com.mapk.annotations.KGetterIgnore +import com.mapk.core.ArgumentBucket +import com.mapk.core.KFunctionForCall +import com.mapk.core.getAliasOrName +import com.mapk.core.isUseDefaultArgument +import com.mapk.core.toKConstructor +import java.lang.reflect.Method +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.KVisibility +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.javaGetter + +// TODO: キャッシュ機能の実装 +class KMapper private constructor( + private val function: KFunctionForCall, + parameterNameConverter: (String) -> String +) { + constructor(function: KFunction, parameterNameConverter: (String) -> String = { it }) : this( + KFunctionForCall(function), parameterNameConverter + ) + + constructor(clazz: KClass, parameterNameConverter: (String) -> String = { it }) : this( + clazz.toKConstructor(), parameterNameConverter + ) + + private val parameterMap: Map> = function.parameters + .filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() } + .associate { (parameterNameConverter(it.getAliasOrName()!!)) to PlainParameterForMap.newInstance(it) } + + private fun bindArguments(argumentBucket: ArgumentBucket, src: Any) { + src::class.memberProperties.forEach outer@{ property -> + // propertyが公開されていない場合は処理を行わない + if (property.visibility != KVisibility.PUBLIC) return@outer + + // ゲッターが取れない場合は処理を行わない + val javaGetter: Method = property.javaGetter ?: return@outer + + var alias: String? = null + // NOTE: IgnoreとAliasが同時に指定されるようなパターンを考慮してaliasが取れてもbreakしていない + javaGetter.annotations.forEach { + if (it is KGetterIgnore) return@outer // ignoreされている場合は処理を行わない + if (it is KGetterAlias) alias = it.value + } + + parameterMap[alias ?: property.name]?.let { + // javaGetterを呼び出す方が高速 + javaGetter.isAccessible = true + argumentBucket.putIfAbsent(it.param, javaGetter.invoke(src)?.let { value -> it.mapObject(value) }) + // 終了判定 + if (argumentBucket.isInitialized) return + } + } + } + + private fun bindArguments(argumentBucket: ArgumentBucket, src: Map<*, *>) { + src.forEach { (key, value) -> + parameterMap[key]?.let { param -> + // 取得した内容がnullでなければ適切にmapする + argumentBucket.putIfAbsent(param.param, value?.let { param.mapObject(value) }) + // 終了判定 + if (argumentBucket.isInitialized) return + } + } + } + + private fun bindArguments(argumentBucket: ArgumentBucket, srcPair: Pair<*, *>) { + parameterMap[srcPair.first.toString()]?.let { + argumentBucket.putIfAbsent(it.param, srcPair.second?.let { value -> it.mapObject(value) }) + } + } + + fun map(srcMap: Map): T { + val bucket: ArgumentBucket = function.getArgumentBucket() + bindArguments(bucket, srcMap) + + return function.call(bucket) + } + + fun map(srcPair: Pair): T { + val bucket: ArgumentBucket = function.getArgumentBucket() + bindArguments(bucket, srcPair) + + return function.call(bucket) + } + + fun map(src: Any): T { + val bucket: ArgumentBucket = function.getArgumentBucket() + bindArguments(bucket, src) + + return function.call(bucket) + } + + fun map(vararg args: Any): T { + val bucket: ArgumentBucket = function.getArgumentBucket() + + listOf(*args).forEach { arg -> + when (arg) { + is Map<*, *> -> bindArguments(bucket, arg) + is Pair<*, *> -> bindArguments(bucket, arg) + else -> bindArguments(bucket, arg) + } + } + + return function.call(bucket) + } +} From 1b27b80cf3b46d5d77a8d559b567a5783c0d29d2 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 11 Apr 2020 23:07:06 +0900 Subject: [PATCH 03/12] =?UTF-8?q?=E6=96=B0=E3=81=97=E3=81=84KMapper?= =?UTF-8?q?=E5=90=91=E3=81=91=E3=81=AB=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E3=82=B3=E3=83=94=E3=83=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mapk/kmapper/ConverterKMapperTest.kt | 31 +++++++ .../com/mapk/kmapper/DefaultArgumentTest.kt | 10 +++ .../com/mapk/kmapper/EnumMappingTest.kt | 14 +++ .../com/mapk/kmapper/KGetterIgnoreTest.kt | 19 ++++ .../kmapper/ParameterNameConverterTest.kt | 18 ++++ .../com/mapk/kmapper/PropertyAliasTest.kt | 29 ++++++ .../com/mapk/kmapper/SimpleKMapperTest.kt | 90 +++++++++++++++++++ .../com/mapk/kmapper/StringMappingTest.kt | 10 +++ 8 files changed, 221 insertions(+) diff --git a/src/test/kotlin/com/mapk/kmapper/ConverterKMapperTest.kt b/src/test/kotlin/com/mapk/kmapper/ConverterKMapperTest.kt index c619be7..4f8465e 100644 --- a/src/test/kotlin/com/mapk/kmapper/ConverterKMapperTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/ConverterKMapperTest.kt @@ -31,6 +31,37 @@ private data class BoundStaticMethodConverterSrc(val argument: String) @DisplayName("コンバータ有りでのマッピングテスト") class ConverterKMapperTest { + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + @Test + @DisplayName("コンストラクターでのコンバートテスト") + fun constructorConverterTest() { + val mapper = KMapper(ConstructorConverterDst::class) + val result = mapper.map(mapOf("argument" to 1)) + + assertEquals(ConstructorConverter(1), result.argument) + } + + @Test + @DisplayName("コンパニオンオブジェクトに定義したコンバータでのコンバートテスト") + fun companionConverterTest() { + val mapper = KMapper(CompanionConverterDst::class) + val result = mapper.map(mapOf("argument" to "arg")) + + assertEquals("arg", result.argument.arg) + } + + @Test + @DisplayName("スタティックメソッドに定義したコンバータでのコンバートテスト") + fun staticMethodConverterTest() { + val mapper = KMapper(StaticMethodConverterDst::class) + val result = mapper.map(mapOf("argument" to "1,2,3")) + + assertTrue(intArrayOf(1, 2, 3) contentEquals result.argument.arg) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/DefaultArgumentTest.kt b/src/test/kotlin/com/mapk/kmapper/DefaultArgumentTest.kt index 9720ac1..ae1a1df 100644 --- a/src/test/kotlin/com/mapk/kmapper/DefaultArgumentTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/DefaultArgumentTest.kt @@ -13,6 +13,16 @@ class DefaultArgumentTest { private val src = Src(1, "src") + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + @Test + fun test() { + val result = KMapper(::Dst).map(src) + assertEquals(Dst(1, "default"), result) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/EnumMappingTest.kt b/src/test/kotlin/com/mapk/kmapper/EnumMappingTest.kt index 9515c3f..045d7a2 100644 --- a/src/test/kotlin/com/mapk/kmapper/EnumMappingTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/EnumMappingTest.kt @@ -16,6 +16,20 @@ private class EnumMappingDst(val language: JvmLanguage?) @DisplayName("文字列 -> Enumのマッピングテスト") class EnumMappingTest { + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + private val mapper = KMapper(EnumMappingDst::class) + + @ParameterizedTest(name = "Non-Null要求") + @EnumSource(value = JvmLanguage::class) + fun test(language: JvmLanguage) { + val result = mapper.map("language" to language.name) + + assertEquals(language, result.language) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/KGetterIgnoreTest.kt b/src/test/kotlin/com/mapk/kmapper/KGetterIgnoreTest.kt index a0de4e8..85b68cd 100644 --- a/src/test/kotlin/com/mapk/kmapper/KGetterIgnoreTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/KGetterIgnoreTest.kt @@ -13,6 +13,25 @@ class KGetterIgnoreTest { data class Dst(val arg1: Int, val arg2: String, val arg3: Int, val arg4: String) + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + @Test + @DisplayName("フィールドを無視するテスト") + fun test() { + val src1 = Src1(1, "2-1", 31) + val src2 = Src2("2-2", 32, "4") + + val mapper = KMapper(::Dst) + + val dst1 = mapper.map(src1, src2) + val dst2 = mapper.map(src2, src1) + + assertTrue(dst1 == dst2) + assertEquals(Dst(1, "2-1", 32, "4"), dst1) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/ParameterNameConverterTest.kt b/src/test/kotlin/com/mapk/kmapper/ParameterNameConverterTest.kt index b243ebd..ecac694 100644 --- a/src/test/kotlin/com/mapk/kmapper/ParameterNameConverterTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/ParameterNameConverterTest.kt @@ -11,6 +11,24 @@ private data class BoundSrc(val camel_case: String) @DisplayName("パラメータ名変換のテスト") class ParameterNameConverterTest { + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + @Test + @DisplayName("スネークケースsrc -> キャメルケースdst") + fun test() { + val expected = "snakeCase" + val src = mapOf("camel_case" to expected) + + val mapper = KMapper(CamelCaseDst::class) { + CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, it) + } + val result = mapper.map(src) + + assertEquals(expected, result.camelCase) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/PropertyAliasTest.kt b/src/test/kotlin/com/mapk/kmapper/PropertyAliasTest.kt index 23638b7..ec61bfd 100644 --- a/src/test/kotlin/com/mapk/kmapper/PropertyAliasTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/PropertyAliasTest.kt @@ -20,6 +20,35 @@ private data class AliasedSrc( @DisplayName("エイリアスを貼った場合のテスト") class PropertyAliasTest { + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + @Test + @DisplayName("パラメータにエイリアスを貼った場合") + fun paramAliasTest() { + val src = mapOf( + "arg1" to 1.0, + "arg2" to "2", + "arg3" to 3 + ) + + val result = KMapper(::AliasedDst).map(src) + + assertEquals(1.0, result.arg1) + assertEquals(3, result.arg2) + } + + @Test + @DisplayName("ゲッターにエイリアスを貼った場合") + fun getAliasTest() { + val src = AliasedSrc(1.0, 2) + val result = KMapper(::AliasedDst).map(src) + + assertEquals(1.0, result.arg1) + assertEquals(2, result.arg2) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/SimpleKMapperTest.kt b/src/test/kotlin/com/mapk/kmapper/SimpleKMapperTest.kt index 12a0169..a26ce59 100644 --- a/src/test/kotlin/com/mapk/kmapper/SimpleKMapperTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/SimpleKMapperTest.kt @@ -73,6 +73,96 @@ class SimpleKMapperTest { return SimpleDst(arg1, arg2, arg3) } + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + private val mappers: Set> = setOf( + KMapper(SimpleDst::class), + KMapper(::SimpleDst), + KMapper((SimpleDst)::factory), + KMapper(::instanceFunction), + KMapper(SimpleDstExt::class) + ) + + @Nested + @DisplayName("Mapからマップ") + inner class FromMap { + @Test + @DisplayName("Nullを含まない場合") + fun testWithoutNull() { + val srcMap: Map = mapOf( + "arg1" to 2, + "arg2" to "value", + "arg3" to 1.0 + ) + + val dsts = mappers.map { it.map(srcMap) } + + assertEquals(1, dsts.distinct().size) + dsts.first().let { + assertEquals(2, it.arg1) + assertEquals("value", it.arg2) + assertEquals(1.0, it.arg3) + } + } + + @Test + @DisplayName("Nullを含む場合") + fun testContainsNull() { + val srcMap: Map = mapOf( + "arg1" to 1, + "arg2" to null, + "arg3" to 2.0f + ) + + val dsts = mappers.map { it.map(srcMap) } + + assertEquals(1, dsts.distinct().size) + dsts.first().let { + assertEquals(1, it.arg1) + assertEquals(null, it.arg2) + assertEquals(2.0f, it.arg3) + } + } + } + + @Nested + @DisplayName("インスタンスからマップ") + inner class FromInstance { + @Test + @DisplayName("Nullを含まない場合") + fun testWithoutNull() { + val stringValue = "value" + + val src = Src1(stringValue) + + val dsts = mappers.map { it.map(src) } + + assertEquals(1, dsts.distinct().size) + dsts.first().let { + assertEquals(stringValue.length, it.arg1) + assertEquals(stringValue, it.arg2) + assertEquals(stringValue.length.toByte(), it.arg3) + } + } + + @Test + @DisplayName("Nullを含む場合") + fun testContainsNull() { + val src = Src1(null) + + val dsts = mappers.map { it.map(src) } + + assertEquals(1, dsts.distinct().size) + dsts.first().let { + assertEquals(0, it.arg1) + assertEquals(null, it.arg2) + assertEquals(0.toByte(), it.arg3) + } + } + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { diff --git a/src/test/kotlin/com/mapk/kmapper/StringMappingTest.kt b/src/test/kotlin/com/mapk/kmapper/StringMappingTest.kt index 0488639..ff42fe2 100644 --- a/src/test/kotlin/com/mapk/kmapper/StringMappingTest.kt +++ b/src/test/kotlin/com/mapk/kmapper/StringMappingTest.kt @@ -10,6 +10,16 @@ private data class BoundMappingSrc(val value: Int) @DisplayName("文字列に対してtoStringしたものを渡すテスト") class StringMappingTest { + @Nested + @DisplayName("KMapper") + inner class KMapperTest { + @Test + fun test() { + val result: StringMappingDst = KMapper(StringMappingDst::class).map("value" to 1) + assertEquals("1", result.value) + } + } + @Nested @DisplayName("PlainKMapper") inner class PlainKMapperTest { From 81495043af3c124bff5fd7a7fd3810091ff2bd8f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:15:20 +0900 Subject: [PATCH 04/12] =?UTF-8?q?PlainParameterForMap=E3=81=8B=E3=82=89?= =?UTF-8?q?=E3=82=B3=E3=83=94=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mapk/kmapper/ParameterForMap.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt new file mode 100644 index 0000000..e7d32a9 --- /dev/null +++ b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt @@ -0,0 +1,47 @@ +package com.mapk.kmapper + +import com.mapk.core.EnumMapper +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.full.isSuperclassOf + +internal class ParameterForMap private constructor(val param: KParameter, private val clazz: KClass) { + private val javaClazz: Class by lazy { + clazz.java + } + // リストの長さが小さいと期待されるためこの形で実装しているが、理想的にはmap的なものが使いたい + private val converters: Set, KFunction>> by lazy { + convertersFromConstructors(clazz) + convertersFromStaticMethods(clazz) + convertersFromCompanionObject(clazz) + } + + fun mapObject(value: U): Any? { + val valueClazz: KClass<*> = value::class + + // パラメータに対してvalueが代入可能(同じもしくは親クラス)であればそのまま用いる + if (clazz.isSuperclassOf(valueClazz)) return value + + val converter: KFunction<*>? = getConverter(valueClazz) + + return when { + // converterに一致する組み合わせが有れば設定されていればそれを使う + converter != null -> converter.call(value) + // 要求された値がenumかつ元が文字列ならenum mapperでマップ + javaClazz.isEnum && value is String -> EnumMapper.getEnum(javaClazz, value) + // 要求されているパラメータがStringならtoStringする + clazz == String::class -> value.toString() + else -> throw IllegalArgumentException("Can not convert $valueClazz to $clazz") + } + } + + // 引数の型がconverterに対して入力可能ならconverterを返す + private fun getConverter(input: KClass): KFunction? = + converters.find { (key, _) -> input.isSubclassOf(key) }?.second + + companion object { + fun newInstance(param: KParameter): ParameterForMap<*> { + return ParameterForMap(param, param.type.classifier as KClass<*>) + } + } +} From 9616404f31a742a7e5f774cfa2f986c67acf3342 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:15:42 +0900 Subject: [PATCH 05/12] =?UTF-8?q?=E5=88=A9=E7=94=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/mapk/kmapper/KMapper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/mapk/kmapper/KMapper.kt b/src/main/kotlin/com/mapk/kmapper/KMapper.kt index f51f76a..63b5bc2 100644 --- a/src/main/kotlin/com/mapk/kmapper/KMapper.kt +++ b/src/main/kotlin/com/mapk/kmapper/KMapper.kt @@ -28,9 +28,9 @@ class KMapper private constructor( clazz.toKConstructor(), parameterNameConverter ) - private val parameterMap: Map> = function.parameters + private val parameterMap: Map> = function.parameters .filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() } - .associate { (parameterNameConverter(it.getAliasOrName()!!)) to PlainParameterForMap.newInstance(it) } + .associate { (parameterNameConverter(it.getAliasOrName()!!)) to ParameterForMap.newInstance(it) } private fun bindArguments(argumentBucket: ArgumentBucket, src: Any) { src::class.memberProperties.forEach outer@{ property -> From 23fd133e967d04c10c736edfb60df3ff590361a0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:16:38 +0900 Subject: [PATCH 06/12] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E3=81=8C=E6=9C=89=E3=82=8B=20=3D=20=E4=BD=BF=E3=81=86?= =?UTF-8?q?=E3=81=A8=E8=A6=8B=E5=81=9A=E3=81=97=E3=81=A6lazy=E3=82=92?= =?UTF-8?q?=E8=A7=A3=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt index e7d32a9..f90dcd1 100644 --- a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt @@ -12,9 +12,9 @@ internal class ParameterForMap private constructor(val param: KParamete clazz.java } // リストの長さが小さいと期待されるためこの形で実装しているが、理想的にはmap的なものが使いたい - private val converters: Set, KFunction>> by lazy { - convertersFromConstructors(clazz) + convertersFromStaticMethods(clazz) + convertersFromCompanionObject(clazz) - } + private val converters: Set, KFunction>> = convertersFromConstructors(clazz) + + convertersFromStaticMethods(clazz) + + convertersFromCompanionObject(clazz) fun mapObject(value: U): Any? { val valueClazz: KClass<*> = value::class From 34d04f1709f7c8be6c71972b34a0e74e4de5e479 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:26:48 +0900 Subject: [PATCH 07/12] =?UTF-8?q?=E5=8F=96=E5=BE=97=E6=99=82=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=8F=9B=E5=87=A6=E7=90=86=E3=82=92=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mapk/kmapper/ParameterForMap.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt index f90dcd1..79a8349 100644 --- a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt @@ -16,23 +16,33 @@ internal class ParameterForMap private constructor(val param: KParamete convertersFromStaticMethods(clazz) + convertersFromCompanionObject(clazz) + private val convertCache: MutableMap, (Any) -> Any?> = HashMap() + fun mapObject(value: U): Any? { val valueClazz: KClass<*> = value::class + // 取得方法のキャッシュが有ればそれを用いる + convertCache[valueClazz]?.let { return it(value) } + // パラメータに対してvalueが代入可能(同じもしくは親クラス)であればそのまま用いる - if (clazz.isSuperclassOf(valueClazz)) return value + if (clazz.isSuperclassOf(valueClazz)) { + convertCache[valueClazz] = { value } + return value + } val converter: KFunction<*>? = getConverter(valueClazz) - return when { + val lambda: (Any) -> Any? = when { // converterに一致する組み合わせが有れば設定されていればそれを使う - converter != null -> converter.call(value) + converter != null -> { { converter.call(it) } } // 要求された値がenumかつ元が文字列ならenum mapperでマップ - javaClazz.isEnum && value is String -> EnumMapper.getEnum(javaClazz, value) + javaClazz.isEnum && value is String -> { { EnumMapper.getEnum(javaClazz, it as String) } } // 要求されているパラメータがStringならtoStringする - clazz == String::class -> value.toString() + clazz == String::class -> { { it.toString() } } else -> throw IllegalArgumentException("Can not convert $valueClazz to $clazz") } + convertCache[valueClazz] = lambda + return lambda(value) } // 引数の型がconverterに対して入力可能ならconverterを返す From 1ef5c105a7bfa18a601ce0518da5c30691072878 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:26:55 +0900 Subject: [PATCH 08/12] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/mapk/kmapper/KMapper.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/mapk/kmapper/KMapper.kt b/src/main/kotlin/com/mapk/kmapper/KMapper.kt index 63b5bc2..dc9461e 100644 --- a/src/main/kotlin/com/mapk/kmapper/KMapper.kt +++ b/src/main/kotlin/com/mapk/kmapper/KMapper.kt @@ -15,7 +15,6 @@ import kotlin.reflect.KVisibility import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.javaGetter -// TODO: キャッシュ機能の実装 class KMapper private constructor( private val function: KFunctionForCall, parameterNameConverter: (String) -> String From c08467b9c2efb060848aec0027938440eb691eb6 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:46:10 +0900 Subject: [PATCH 09/12] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E5=8F=96=E5=BE=97=E5=87=A6=E7=90=86=E3=82=92=E5=88=87?= =?UTF-8?q?=E3=82=8A=E5=87=BA=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ついでに関数のスコープを整理 --- src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt b/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt index 4e7dcb7..7e723f2 100644 --- a/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt +++ b/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt @@ -9,7 +9,10 @@ import kotlin.reflect.full.functions import kotlin.reflect.full.staticFunctions import kotlin.reflect.jvm.isAccessible -internal fun Collection>.getConverterMapFromFunctions(): Set, KFunction>> { +internal fun KClass.getConverters(): Set, KFunction>> = + convertersFromConstructors(this) + convertersFromStaticMethods(this) + convertersFromCompanionObject(this) + +private fun Collection>.getConverterMapFromFunctions(): Set, KFunction>> { return filter { it.annotations.any { annotation -> annotation is KConverter } } .map { func -> func.isAccessible = true @@ -18,19 +21,19 @@ internal fun Collection>.getConverterMapFromFunctions(): Set convertersFromConstructors(clazz: KClass): Set, KFunction>> { +private fun convertersFromConstructors(clazz: KClass): Set, KFunction>> { return clazz.constructors.getConverterMapFromFunctions() } @Suppress("UNCHECKED_CAST") -internal fun convertersFromStaticMethods(clazz: KClass): Set, KFunction>> { +private fun convertersFromStaticMethods(clazz: KClass): Set, KFunction>> { val staticFunctions: Collection> = clazz.staticFunctions as Collection> return staticFunctions.getConverterMapFromFunctions() } @Suppress("UNCHECKED_CAST") -internal fun convertersFromCompanionObject(clazz: KClass): Set, KFunction>> { +private fun convertersFromCompanionObject(clazz: KClass): Set, KFunction>> { return clazz.companionObjectInstance?.let { companionObject -> companionObject::class.functions .filter { it.annotations.any { annotation -> annotation is KConverter } } From d73374cc52b2d6961befa0a1b4c3adf49ea0a3fd Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:46:26 +0900 Subject: [PATCH 10/12] =?UTF-8?q?=E6=8B=A1=E5=BC=B5=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E6=96=B9=E3=82=92=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt | 4 +--- src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt | 4 +--- src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt index e61d775..03d0d96 100644 --- a/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt @@ -21,9 +21,7 @@ internal class BoundParameterForMap(val param: KParameter, property: KP val paramClazz = param.type.classifier as KClass<*> val propertyClazz = property.returnType.classifier as KClass<*> - val converter = (convertersFromConstructors(paramClazz) + - convertersFromStaticMethods(paramClazz) + - convertersFromCompanionObject(paramClazz)) + val converter = paramClazz.getConverters() .filter { (key, _) -> propertyClazz.isSubclassOf(key) } .let { if (1 < it.size) throw IllegalArgumentException("${param.name} has multiple converter. $it") diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt index 79a8349..69fc04a 100644 --- a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt @@ -12,9 +12,7 @@ internal class ParameterForMap private constructor(val param: KParamete clazz.java } // リストの長さが小さいと期待されるためこの形で実装しているが、理想的にはmap的なものが使いたい - private val converters: Set, KFunction>> = convertersFromConstructors(clazz) + - convertersFromStaticMethods(clazz) + - convertersFromCompanionObject(clazz) + private val converters: Set, KFunction>> = clazz.getConverters() private val convertCache: MutableMap, (Any) -> Any?> = HashMap() diff --git a/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt index 9c95bb3..ac4b89d 100644 --- a/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt @@ -12,9 +12,7 @@ internal class PlainParameterForMap private constructor(val param: KPar clazz.java } // リストの長さが小さいと期待されるためこの形で実装しているが、理想的にはmap的なものが使いたい - private val converters: Set, KFunction>> by lazy { - convertersFromConstructors(clazz) + convertersFromStaticMethods(clazz) + convertersFromCompanionObject(clazz) - } + private val converters: Set, KFunction>> = clazz.getConverters() fun mapObject(value: U): Any? { val valueClazz: KClass<*> = value::class From bbea5e45b7ede049d1271043c8f5ecbdde8b0ab4 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:49:59 +0900 Subject: [PATCH 11/12] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E6=A4=9C=E7=B4=A2=E5=87=A6=E7=90=86=E3=82=92=E5=88=87?= =?UTF-8?q?=E3=82=8A=E5=87=BA=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt b/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt index 7e723f2..be1e830 100644 --- a/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt +++ b/src/main/kotlin/com/mapk/kmapper/ParameterUtils.kt @@ -6,6 +6,7 @@ import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.full.companionObjectInstance import kotlin.reflect.full.functions +import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.staticFunctions import kotlin.reflect.jvm.isAccessible @@ -47,3 +48,7 @@ private fun convertersFromCompanionObject(clazz: KClass): Set Set, KFunction>>.getConverter(input: KClass): KFunction? = + this.find { (key, _) -> input.isSubclassOf(key) }?.second From 5663adeeebd45b0ee97e520bbcf8ac53a5f3a8cf Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 12 Apr 2020 03:50:36 +0900 Subject: [PATCH 12/12] =?UTF-8?q?=E6=8B=A1=E5=BC=B5=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E6=96=B9=E3=82=92=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BoundKMapperに関しては別扱いの処理なので修正していない --- src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt | 7 +------ src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt index 69fc04a..4dd300e 100644 --- a/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/ParameterForMap.kt @@ -4,7 +4,6 @@ import com.mapk.core.EnumMapper import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter -import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSuperclassOf internal class ParameterForMap private constructor(val param: KParameter, private val clazz: KClass) { @@ -28,7 +27,7 @@ internal class ParameterForMap private constructor(val param: KParamete return value } - val converter: KFunction<*>? = getConverter(valueClazz) + val converter: KFunction<*>? = converters.getConverter(valueClazz) val lambda: (Any) -> Any? = when { // converterに一致する組み合わせが有れば設定されていればそれを使う @@ -43,10 +42,6 @@ internal class ParameterForMap private constructor(val param: KParamete return lambda(value) } - // 引数の型がconverterに対して入力可能ならconverterを返す - private fun getConverter(input: KClass): KFunction? = - converters.find { (key, _) -> input.isSubclassOf(key) }?.second - companion object { fun newInstance(param: KParameter): ParameterForMap<*> { return ParameterForMap(param, param.type.classifier as KClass<*>) diff --git a/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt b/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt index ac4b89d..1c9a880 100644 --- a/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt +++ b/src/main/kotlin/com/mapk/kmapper/PlainParameterForMap.kt @@ -4,7 +4,6 @@ import com.mapk.core.EnumMapper import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter -import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSuperclassOf internal class PlainParameterForMap private constructor(val param: KParameter, private val clazz: KClass) { @@ -20,7 +19,7 @@ internal class PlainParameterForMap private constructor(val param: KPar // パラメータに対してvalueが代入可能(同じもしくは親クラス)であればそのまま用いる if (clazz.isSuperclassOf(valueClazz)) return value - val converter: KFunction<*>? = getConverter(valueClazz) + val converter: KFunction<*>? = converters.getConverter(valueClazz) return when { // converterに一致する組み合わせが有れば設定されていればそれを使う @@ -33,10 +32,6 @@ internal class PlainParameterForMap private constructor(val param: KPar } } - // 引数の型がconverterに対して入力可能ならconverterを返す - private fun getConverter(input: KClass): KFunction? = - converters.find { (key, _) -> input.isSubclassOf(key) }?.second - companion object { fun newInstance(param: KParameter): PlainParameterForMap<*> { return PlainParameterForMap(param, param.type.classifier as KClass<*>)