diff --git a/build.gradle.kts b/build.gradle.kts index b48580b..9c71270 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,13 @@ plugins { id("maven") id("java") - id("org.jetbrains.kotlin.jvm") version "1.4.0" + id("org.jetbrains.kotlin.jvm") version "1.4.10" id("org.jlleitschuh.gradle.ktlint") version "9.3.0" id("jacoco") } group = "com.mapk" -version = "0.17" +version = "0.18" java { sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/src/main/kotlin/com/mapk/core/KFunctionForCall.kt b/src/main/kotlin/com/mapk/core/KFunctionForCall.kt index 4282305..9bd9a2b 100644 --- a/src/main/kotlin/com/mapk/core/KFunctionForCall.kt +++ b/src/main/kotlin/com/mapk/core/KFunctionForCall.kt @@ -3,6 +3,7 @@ package com.mapk.core import com.mapk.annotations.KParameterFlatten import com.mapk.core.internal.ArgumentBinder import com.mapk.core.internal.BucketGenerator +import com.mapk.core.internal.FullInitializedFunctionWrapper import com.mapk.core.internal.ParameterNameConverter import com.mapk.core.internal.getAliasOrName import com.mapk.core.internal.getKConstructor @@ -26,6 +27,9 @@ class KFunctionForCall internal constructor( instance ) + @TestOnly + internal val fullInitializedWrapper: FullInitializedFunctionWrapper + @TestOnly internal val parameters: List = function.parameters @@ -42,6 +46,8 @@ class KFunctionForCall internal constructor( // この関数には確実にアクセスするためアクセシビリティ書き換え function.isAccessible = true + fullInitializedWrapper = FullInitializedFunctionWrapper(function, instance, parameters.size) + val tempBinders = ArrayList() val tempList = ArrayList>() val tempMap = HashMap>() @@ -80,7 +86,7 @@ class KFunctionForCall internal constructor( fun call(adaptor: ArgumentAdaptor): T { val bucket = bucketGenerator.generate(adaptor) - return if (bucket.isInitialized) function.call(*bucket.valueArray) else function.callBy(bucket) + return if (bucket.isInitialized) fullInitializedWrapper.call(bucket.valueArray) else function.callBy(bucket) } } diff --git a/src/main/kotlin/com/mapk/core/internal/ArgumentBucket.kt b/src/main/kotlin/com/mapk/core/internal/ArgumentBucket.kt index 26d3fc9..9f02a6d 100644 --- a/src/main/kotlin/com/mapk/core/internal/ArgumentBucket.kt +++ b/src/main/kotlin/com/mapk/core/internal/ArgumentBucket.kt @@ -7,7 +7,7 @@ import kotlin.reflect.KParameter internal class ArgumentBucket( private val keyList: List, val valueArray: Array, - initializationStatus: Array, + initializationStatus: BooleanArray, argumentBinders: List, adaptor: ArgumentAdaptor ) : Map { diff --git a/src/main/kotlin/com/mapk/core/internal/BucketGenerator.kt b/src/main/kotlin/com/mapk/core/internal/BucketGenerator.kt index 3356233..a629bb1 100644 --- a/src/main/kotlin/com/mapk/core/internal/BucketGenerator.kt +++ b/src/main/kotlin/com/mapk/core/internal/BucketGenerator.kt @@ -9,7 +9,7 @@ internal class BucketGenerator( instance: Any? ) { private val originalValueArray: Array = arrayOfNulls(parameters.size) - private val originalInitializationStatus: Array = Array(parameters.size) { false } + private val originalInitializationStatus: BooleanArray = BooleanArray(parameters.size) init { if (instance != null) { diff --git a/src/main/kotlin/com/mapk/core/internal/FullInitializedFunctionWrapper.kt b/src/main/kotlin/com/mapk/core/internal/FullInitializedFunctionWrapper.kt new file mode 100644 index 0000000..3f6369e --- /dev/null +++ b/src/main/kotlin/com/mapk/core/internal/FullInitializedFunctionWrapper.kt @@ -0,0 +1,29 @@ +package com.mapk.core.internal + +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.javaConstructor +import kotlin.reflect.jvm.javaMethod + +internal class FullInitializedFunctionWrapper(function: KFunction, instance: Any?, paramSize: Int) { + private val lambda: (Array) -> T + + init { + val constructor = function.javaConstructor + + lambda = when { + constructor != null -> { + { constructor.newInstance(*it) } + } + instance != null -> { + val method = function.javaMethod!! + + @Suppress("UNCHECKED_CAST") { method.invoke(instance, *(it.copyOfRange(1, paramSize))) as T } + } + else -> { + { function.call(*it) } + } + } + } + + fun call(args: Array): T = lambda(args) +} diff --git a/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt b/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt index 605d5f5..b324f02 100644 --- a/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt +++ b/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt @@ -51,7 +51,6 @@ class KFunctionForCallTest { fun fromCompanionObject() { val function = Companion::class.functions .first { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name } - .let { spyk(it) } val kFunctionForCall = KFunctionForCall(function, { it }, Companion) @@ -60,7 +59,6 @@ class KFunctionForCallTest { adaptor.putIfAbsent("arg2", 2) val result = kFunctionForCall.call(adaptor) assertEquals("12", result) - verify(exactly = 1) { function.call(*anyVararg()) } } private fun func(key: String, value: String = "default"): Pair = key to value @@ -84,7 +82,6 @@ class KFunctionForCallTest { fun multipleCall() { val function = Companion::class.functions .first { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name } - .let { spyk(it) } val kFunctionForCall = KFunctionForCall(function, { it }, Companion) @@ -101,8 +98,6 @@ class KFunctionForCallTest { .forEach { adaptor2.putIfAbsent(it.name!!, it.index + 1) } val result2 = kFunctionForCall.call(adaptor2) assertEquals("23", result2) - - verify(exactly = 2) { function.call(*anyVararg()) } } } diff --git a/src/test/kotlin/com/mapk/core/KParameterFlattenTest.kt b/src/test/kotlin/com/mapk/core/KParameterFlattenTest.kt index a3582a3..be3d0a3 100644 --- a/src/test/kotlin/com/mapk/core/KParameterFlattenTest.kt +++ b/src/test/kotlin/com/mapk/core/KParameterFlattenTest.kt @@ -2,8 +2,6 @@ package com.mapk.core import com.mapk.annotations.KConstructor import com.mapk.annotations.KParameterFlatten -import io.mockk.spyk -import io.mockk.verify import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DisplayName @@ -44,8 +42,7 @@ class KParameterFlattenTest { @Test fun test() { - val spiedFunction = spyk(::Dst) - val function = KFunctionForCall(spiedFunction, { it }) + val function = KFunctionForCall(::Dst, { it }) function.requiredParameters.forEach { assertTrue(expectedParams.contains(it.name)) @@ -59,6 +56,5 @@ class KParameterFlattenTest { val actual = function.call(adaptor) assertEquals(expected, actual) - verify(exactly = 1) { spiedFunction.call(*anyVararg()) } } } diff --git a/src/test/kotlin/com/mapk/core/internal/FullInitializedFunctionWrapperTest.kt b/src/test/kotlin/com/mapk/core/internal/FullInitializedFunctionWrapperTest.kt new file mode 100644 index 0000000..b832344 --- /dev/null +++ b/src/test/kotlin/com/mapk/core/internal/FullInitializedFunctionWrapperTest.kt @@ -0,0 +1,56 @@ +package com.mapk.core.internal + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.full.functions + +@DisplayName("完全初期化時呼び出しのテスト") +class FullInitializedFunctionWrapperTest { + data class Dst(val foo: Int, val bar: String) { + companion object { + fun of(foo: Int, bar: String) = Dst(foo, bar) + } + } + fun instanceMethod(foo: Int, bar: String) = Dst(foo, bar) + private val expected = Dst(1, "2") + + @Test + @DisplayName("コンストラクタの場合") + fun constructorTest() { + val fullInitializedFunctionWrapper = FullInitializedFunctionWrapper(::Dst, null, 2) + assertEquals(expected, fullInitializedFunctionWrapper.call(arrayOf(1, "2"))) + } + + @Test + @DisplayName("コンパニオンオブジェクトに定義した関数の場合") + fun companionObjectFunTest() { + val func = Dst::class.companionObject!!.functions.first { it.name == "of" } + val instance = Dst::class.companionObjectInstance!! + val fullInitializedFunctionWrapper = FullInitializedFunctionWrapper( + func, instance, 3 + ) + assertEquals(expected, fullInitializedFunctionWrapper.call(arrayOf(instance, 1, "2"))) + } + + @Nested + @DisplayName("その他の場合") + inner class OthersTest { + @Test + @DisplayName("コンパニオンオブジェクトに定義した関数をメソッドリファレンスで取得した場合") + fun companionObjectFunByMethodReferenceTest() { + val fullInitializedFunctionWrapper = FullInitializedFunctionWrapper((Dst)::of, null, 2) + assertEquals(expected, fullInitializedFunctionWrapper.call(arrayOf(1, "2"))) + } + + @Test + @DisplayName("インスタンスメソッドの場合") + fun instanceMethodTest() { + val fullInitializedFunctionWrapper = FullInitializedFunctionWrapper(::instanceMethod, null, 2) + assertEquals(expected, fullInitializedFunctionWrapper.call(arrayOf(1, "2"))) + } + } +}