Skip to content
This repository was archived by the owner on Jan 20, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions src/main/kotlin/com/mapk/core/ArgumentBucket.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
package com.mapk.core

import kotlin.reflect.KParameter

internal class BucketGenerator(
private val bucket: Array<Any?>,
private val instancePair: Pair<KParameter, Any?>?,
private val initializationStatus: Int,
private val initializeMask: List<Int>
) {
private val completionValue: Int = initializeMask.reduce { l, r -> l or r }
private val bucketSize = bucket.size

fun generate(): ArgumentBucket {
val tempMap = HashMap<KParameter, Any?>(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<Any?>,
internal val bucketMap: MutableMap<KParameter, Any?>,
private var initializationStatus: Int,
private val initializeMask: List<Int>,
// 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<Int> 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
)
}
}
36 changes: 16 additions & 20 deletions src/main/kotlin/com/mapk/core/KFunctionForCall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,32 @@ import kotlin.reflect.jvm.isAccessible

class KFunctionForCall<T>(private val function: KFunction<T>, instance: Any? = null) {
val parameters: List<KParameter> = function.parameters
private val originalArgumentBucket: ArgumentBucket
private val generator: BucketGenerator

init {
if (parameters.isEmpty() || (instance != null && parameters.size == 1))
throw IllegalArgumentException("This function is not require arguments.")

// この関数には確実にアクセスするためアクセシビリティ書き換え
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<Any?>(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)
}
43 changes: 10 additions & 33 deletions src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Any?>(), argumentBucket.notInitializedParameterIndexes)
assertTrue(argumentBucket.isInitialized)
}
}

Expand All @@ -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])
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<String, String> = 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()
}
}
}