diff --git a/src/main/kotlin/com/mapk/core/ArgumentBucket.kt b/src/main/kotlin/com/mapk/core/ArgumentBucket.kt index 6ad9eb8..a08c25a 100644 --- a/src/main/kotlin/com/mapk/core/ArgumentBucket.kt +++ b/src/main/kotlin/com/mapk/core/ArgumentBucket.kt @@ -1,33 +1,48 @@ package com.mapk.core +import kotlin.reflect.KParameter + +internal class BucketGenerator( + private val bucket: Array, + private val instancePair: Pair?, + private val initializationStatus: Int, + private val initializeMask: List +) { + private val completionValue: Int = initializeMask.reduce { l, r -> l or r } + private val bucketSize = bucket.size + + fun generate(): ArgumentBucket { + val tempMap = HashMap(bucketSize, 1.0f) + instancePair?.run { tempMap[this.first] = this.second } + + return ArgumentBucket( + bucket.copyOf(), + tempMap, + initializationStatus, + initializeMask, + completionValue + ) + } +} + class ArgumentBucket internal constructor( internal val bucket: Array, + internal val bucketMap: MutableMap, private var initializationStatus: Int, private val initializeMask: List, - // clone時の再計算を避けるため1回で済むようにデフォルト値化 - private val completionValue: Int = initializeMask.reduce { l, r -> l or r } -) : Cloneable { + private val completionValue: Int +) { val isInitialized: Boolean get() = initializationStatus == completionValue - val notInitializedParameterIndexes: List get() = initializeMask.indices.filter { - initializationStatus and initializeMask[it] == 0 - } - fun setArgument(argument: Any?, index: Int) { + fun setArgument(kParameter: KParameter, argument: Any?) { + val index = kParameter.index val temp = initializationStatus or initializeMask[index] // 先に入ったものを優先するため、初期化済みなら何もしない if (initializationStatus == temp) return + bucketMap[kParameter] = argument bucket[index] = argument initializationStatus = temp } - - public override fun clone(): ArgumentBucket { - return ArgumentBucket( - bucket.copyOf(), - initializationStatus, - initializeMask, - completionValue - ) - } } diff --git a/src/main/kotlin/com/mapk/core/KFunctionForCall.kt b/src/main/kotlin/com/mapk/core/KFunctionForCall.kt index cfc8281..47cc3e4 100644 --- a/src/main/kotlin/com/mapk/core/KFunctionForCall.kt +++ b/src/main/kotlin/com/mapk/core/KFunctionForCall.kt @@ -6,7 +6,7 @@ import kotlin.reflect.jvm.isAccessible class KFunctionForCall(private val function: KFunction, instance: Any? = null) { val parameters: List = function.parameters - private val originalArgumentBucket: ArgumentBucket + private val generator: BucketGenerator init { if (parameters.isEmpty() || (instance != null && parameters.size == 1)) @@ -14,28 +14,24 @@ class KFunctionForCall(private val function: KFunction, instance: Any? = n // この関数には確実にアクセスするためアクセシビリティ書き換え function.isAccessible = true - originalArgumentBucket = if (instance != null) { - ArgumentBucket( - Array(parameters.size) { if (it == 0) instance else null }, - 1, - generateSequence(1) { it.shl(1) } - .take(parameters.size) - .toList() - ) + + // 初期化処理の共通化のため先に初期化 + val tempArray = Array(parameters.size) { null } + val maskList = generateSequence(1) { it.shl(1) }.take(parameters.size).toList() + + generator = if (instance != null) { + tempArray[0] = instance + + // 引数の1番目は初期化済みということでinitializationStatusは1スタート + BucketGenerator(tempArray, parameters.first { it.kind == KParameter.Kind.INSTANCE } to instance, 1, maskList) } else { - ArgumentBucket( - Array(parameters.size) { null }, - 0, - generateSequence(1) { it.shl(1) } - .take(parameters.size) - .toList() - ) + BucketGenerator(tempArray, null, 0, maskList) } } - fun getArgumentBucket(): ArgumentBucket = originalArgumentBucket.clone() + fun getArgumentBucket(): ArgumentBucket = generator.generate() - fun call(argumentBucket: ArgumentBucket): T { - return function.call(*argumentBucket.bucket) - } + fun call(argumentBucket: ArgumentBucket): T = + if (argumentBucket.isInitialized) function.call(*argumentBucket.bucket) + else function.callBy(argumentBucket.bucketMap) } diff --git a/src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt b/src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt index 21561b4..02561c2 100644 --- a/src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt +++ b/src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt @@ -2,7 +2,6 @@ package com.mapk.core import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertIterableEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName @@ -36,36 +35,11 @@ class ArgumentBucketTest { @Test @DisplayName("初期化後") fun isInitialized() { - argumentBucket.setArgument(object {}, 0) - argumentBucket.setArgument(object {}, 1) - argumentBucket.setArgument(object {}, 2) - assertTrue(argumentBucket.isInitialized) - } - } - - @Nested - @DisplayName("初期化されていないインデックス取得のテスト") - inner class NotInitializedParameterIndexesTest { - @Test - @DisplayName("何もセットしていない場合") - fun noArguments() { - assertIterableEquals(listOf(0, 1, 2), argumentBucket.notInitializedParameterIndexes) - } + ::sampleFunction.parameters.forEach { + argumentBucket.setArgument(it, object {}) + } - @Test - @DisplayName("1つセットした場合") - fun singleArgument() { - argumentBucket.setArgument(object {}, 1) - assertIterableEquals(listOf(0, 2), argumentBucket.notInitializedParameterIndexes) - } - - @Test - @DisplayName("全てセットした場合") - fun fullArguments() { - argumentBucket.setArgument(object {}, 0) - argumentBucket.setArgument(object {}, 1) - argumentBucket.setArgument(object {}, 2) - assertIterableEquals(emptyList(), argumentBucket.notInitializedParameterIndexes) + assertTrue(argumentBucket.isInitialized) } } @@ -75,15 +49,18 @@ class ArgumentBucketTest { @Test @DisplayName("正常に追加した場合") fun setNewArgument() { - argumentBucket.setArgument("argument", 0) + val parameter = ::sampleFunction.parameters.first { it.index == 0 } + argumentBucket.setArgument(parameter, "argument") assertEquals("argument", argumentBucket.bucket[0]) } @Test @DisplayName("同じインデックスに2回追加した場合") fun setArgumentTwice() { - argumentBucket.setArgument("first", 0) - argumentBucket.setArgument("second", 0) + val parameter = ::sampleFunction.parameters.first { it.index == 0 } + + argumentBucket.setArgument(parameter, "first") + argumentBucket.setArgument(parameter, "second") assertEquals("first", argumentBucket.bucket[0]) } } diff --git a/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt b/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt index 3247531..99d2691 100644 --- a/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt +++ b/src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt @@ -1,5 +1,7 @@ package com.mapk.core +import kotlin.reflect.full.functions +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 @@ -33,4 +35,42 @@ class KFunctionForCallTest { assertDoesNotThrow { KFunctionForCall(this::dummy2) } } } + + @Nested + @DisplayName("呼び出し関連テスト") + inner class CallTest { + @Test + @DisplayName("コンパニオンオブジェクトから取得した場合") + fun fromCompanionObject() { + val function = + Companion::class.functions.find { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }!! + + val kFunctionForCall = KFunctionForCall(function, Companion) + + val bucket = kFunctionForCall.getArgumentBucket() + kFunctionForCall.parameters.forEach { bucket.setArgument(it, it.index) } + val result = kFunctionForCall.call(bucket) + assertEquals("12", result) + } + + private fun func(key: String, value: String = "default"): Pair = key to value + + @Test + @DisplayName("デフォルト値を用いる場合") + fun useDefaultValue() { + val kFunctionForCall = KFunctionForCall(::func) + val argumentBucket = kFunctionForCall.getArgumentBucket() + + ::func.parameters.forEach { if (!it.isOptional) argumentBucket.setArgument(it, it.name) } + + val result = kFunctionForCall.call(argumentBucket) + assertEquals("key" to "default", result) + } + } + + companion object { + fun declaredOnCompanionObject(arg1: Any, arg2: Any): String { + return arg1.toString() + arg2.toString() + } + } }