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
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ dependencies {
testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.0") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
// https://mvnrepository.com/artifact/io.mockk/mockk
testImplementation("io.mockk:mockk:1.9.3")
}

tasks {
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/com/mapk/core/BucketGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.mapk.core;

import kotlin.Pair;
import kotlin.reflect.KParameter;

import java.util.ArrayList;
import java.util.List;

class BucketGenerator {
private final int initializationStatus;
private final List<Integer> initializeMask;
private final int completionValue;

private final KParameter[] keyArray;
private final Object[] valueArray;

BucketGenerator(int capacity, Pair<KParameter, Object> instancePair) {
keyArray = new KParameter[capacity];
valueArray = new Object[capacity];

if (instancePair != null) {
initializationStatus = 1;

keyArray[0] = instancePair.getFirst();
valueArray[0] = instancePair.getSecond();
} else {
initializationStatus = 0;
}

initializeMask = new ArrayList<>(capacity);
int completionValue = 0;

for (int i = 0, mask = 1; i < capacity; i++, mask <<= 1) {
initializeMask.add(i, mask);
completionValue |= mask;
}

this.completionValue = completionValue;
}

ArgumentBucket generate() {
return new ArgumentBucket(
keyArray.clone(),
valueArray.clone(),
initializationStatus,
initializeMask,
completionValue
);
}
}
73 changes: 43 additions & 30 deletions src/main/kotlin/com/mapk/core/ArgumentBucket.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,60 @@ 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 val keyArray: Array<KParameter?>,
internal val valueArray: Array<Any?>,
private var initializationStatus: Int,
private val initializeMask: List<Int>,
private val completionValue: Int
) {
) : Map<KParameter, Any?> {
// インスタンス有りなら1、そうでなければ0スタート
private var count: Int = initializationStatus

val isInitialized: Boolean get() = initializationStatus == completionValue

fun setArgument(kParameter: KParameter, argument: Any?) {
val index = kParameter.index
class MutableEntry internal constructor(
override val key: KParameter,
override var value: Any?
) : Map.Entry<KParameter, Any?>

override val size: Int get() = count

override fun containsKey(key: KParameter): Boolean {
// NOTE: もしかしたらステータスを見た方が速いかも
return keyArray[key.index] != null
}

override fun containsValue(value: Any?): Boolean {
throw UnsupportedOperationException()
}

override fun get(key: KParameter): Any? = valueArray[key.index]
fun getByIndex(key: Int): Any? =
if (initializationStatus and initializeMask[key] != 0) valueArray[key]
else throw IllegalStateException("This argument is not initialized.")

override fun isEmpty(): Boolean = count == 0

override val entries: Set<Map.Entry<KParameter, Any?>>
get() = keyArray.mapNotNull { it?.let { MutableEntry(it, valueArray[it.index]) } }.toSet()
override val keys: MutableSet<KParameter>
get() = keyArray.filterNotNull().toMutableSet()
override val values: MutableCollection<Any?>
get() = throw UnsupportedOperationException()

fun putIfAbsent(key: KParameter, value: Any?) {
val index = key.index
val temp = initializationStatus or initializeMask[index]

// 先に入ったものを優先するため、初期化済みなら何もしない
if (initializationStatus == temp) return

bucketMap[kParameter] = argument
bucket[index] = argument
count += 1
initializationStatus = temp
keyArray[index] = key
valueArray[index] = value

return
}
}
15 changes: 4 additions & 11 deletions src/main/kotlin/com/mapk/core/KFunctionForCall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,16 @@ class KFunctionForCall<T>(private val function: KFunction<T>, instance: Any? = n
// この関数には確実にアクセスするためアクセシビリティ書き換え
function.isAccessible = true

// 初期化処理の共通化のため先に初期化
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)
BucketGenerator(parameters.size, parameters.first { it.kind == KParameter.Kind.INSTANCE } to instance)
} else {
BucketGenerator(tempArray, null, 0, maskList)
BucketGenerator(parameters.size, null)
}
}

fun getArgumentBucket(): ArgumentBucket = generator.generate()

fun call(argumentBucket: ArgumentBucket): T =
if (argumentBucket.isInitialized) function.call(*argumentBucket.bucket)
else function.callBy(argumentBucket.bucketMap)
if (argumentBucket.isInitialized) function.call(*argumentBucket.valueArray)
else function.callBy(argumentBucket)
}
12 changes: 6 additions & 6 deletions src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ArgumentBucketTest {
@DisplayName("初期化後")
fun isInitialized() {
::sampleFunction.parameters.forEach {
argumentBucket.setArgument(it, object {})
argumentBucket.putIfAbsent(it, object {})
}

assertTrue(argumentBucket.isInitialized)
Expand All @@ -50,18 +50,18 @@ class ArgumentBucketTest {
@DisplayName("正常に追加した場合")
fun setNewArgument() {
val parameter = ::sampleFunction.parameters.first { it.index == 0 }
argumentBucket.setArgument(parameter, "argument")
assertEquals("argument", argumentBucket.bucket[0])
argumentBucket.putIfAbsent(parameter, "argument")
assertEquals("argument", argumentBucket.getByIndex(0))
}

@Test
@DisplayName("同じインデックスに2回追加した場合")
fun setArgumentTwice() {
val parameter = ::sampleFunction.parameters.first { it.index == 0 }

argumentBucket.setArgument(parameter, "first")
argumentBucket.setArgument(parameter, "second")
assertEquals("first", argumentBucket.bucket[0])
argumentBucket.putIfAbsent(parameter, "first")
argumentBucket.putIfAbsent(parameter, "second")
assertEquals("first", argumentBucket.getByIndex(0))
}
}
}
38 changes: 33 additions & 5 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 io.mockk.spyk
import io.mockk.verify
import kotlin.reflect.full.functions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
Expand Down Expand Up @@ -42,29 +44,55 @@ class KFunctionForCallTest {
@Test
@DisplayName("コンパニオンオブジェクトから取得した場合")
fun fromCompanionObject() {
val function =
Companion::class.functions.find { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }!!
val function = Companion::class.functions
.first { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }
.let { spyk(it) }

val kFunctionForCall = KFunctionForCall(function, Companion)

val bucket = kFunctionForCall.getArgumentBucket()
kFunctionForCall.parameters.forEach { bucket.setArgument(it, it.index) }
kFunctionForCall.parameters.forEach { bucket.putIfAbsent(it, it.index) }
val result = kFunctionForCall.call(bucket)
assertEquals("12", result)
verify(exactly = 1) { function.call(*anyVararg()) }
}

private fun func(key: String, value: String = "default"): Pair<String, String> = key to value

@Test
@DisplayName("デフォルト値を用いる場合")
fun useDefaultValue() {
val kFunctionForCall = KFunctionForCall(::func)
val func = spyk(::func)
val kFunctionForCall = KFunctionForCall(func)
val argumentBucket = kFunctionForCall.getArgumentBucket()

::func.parameters.forEach { if (!it.isOptional) argumentBucket.setArgument(it, it.name) }
func.parameters.forEach { if (!it.isOptional) argumentBucket.putIfAbsent(it, it.name) }

val result = kFunctionForCall.call(argumentBucket)
assertEquals("key" to "default", result)
verify(exactly = 1) { func.callBy(any()) }
}

@Test
@DisplayName("同一関数を違うソースから複数回呼んだ場合")
fun multipleCall() {
val function = Companion::class.functions
.first { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }
.let { spyk(it) }

val kFunctionForCall = KFunctionForCall(function, Companion)

val bucket1 = kFunctionForCall.getArgumentBucket()
kFunctionForCall.parameters.forEach { bucket1.putIfAbsent(it, it.index) }
val result1 = kFunctionForCall.call(bucket1)
assertEquals("12", result1)

val bucket2 = kFunctionForCall.getArgumentBucket()
kFunctionForCall.parameters.forEach { bucket2.putIfAbsent(it, it.index + 1) }
val result2 = kFunctionForCall.call(bucket2)
assertEquals("23", result2)

verify(exactly = 2) { function.call(*anyVararg()) }
}
}

Expand Down