Skip to content
This repository was archived by the owner on Jan 20, 2023. It is now read-only.

Commit d87d4d1

Browse files
authored
Merge pull request #7 from k163377/bucket_feature
Improve Bucket.
2 parents 0c1c158 + f7406be commit d87d4d1

File tree

6 files changed

+138
-52
lines changed

6 files changed

+138
-52
lines changed

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ dependencies {
3434
testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.0") {
3535
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
3636
}
37+
// https://mvnrepository.com/artifact/io.mockk/mockk
38+
testImplementation("io.mockk:mockk:1.9.3")
3739
}
3840

3941
tasks {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.mapk.core;
2+
3+
import kotlin.Pair;
4+
import kotlin.reflect.KParameter;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
class BucketGenerator {
10+
private final int initializationStatus;
11+
private final List<Integer> initializeMask;
12+
private final int completionValue;
13+
14+
private final KParameter[] keyArray;
15+
private final Object[] valueArray;
16+
17+
BucketGenerator(int capacity, Pair<KParameter, Object> instancePair) {
18+
keyArray = new KParameter[capacity];
19+
valueArray = new Object[capacity];
20+
21+
if (instancePair != null) {
22+
initializationStatus = 1;
23+
24+
keyArray[0] = instancePair.getFirst();
25+
valueArray[0] = instancePair.getSecond();
26+
} else {
27+
initializationStatus = 0;
28+
}
29+
30+
initializeMask = new ArrayList<>(capacity);
31+
int completionValue = 0;
32+
33+
for (int i = 0, mask = 1; i < capacity; i++, mask <<= 1) {
34+
initializeMask.add(i, mask);
35+
completionValue |= mask;
36+
}
37+
38+
this.completionValue = completionValue;
39+
}
40+
41+
ArgumentBucket generate() {
42+
return new ArgumentBucket(
43+
keyArray.clone(),
44+
valueArray.clone(),
45+
initializationStatus,
46+
initializeMask,
47+
completionValue
48+
);
49+
}
50+
}

src/main/kotlin/com/mapk/core/ArgumentBucket.kt

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,60 @@ package com.mapk.core
22

33
import kotlin.reflect.KParameter
44

5-
internal class BucketGenerator(
6-
private val bucket: Array<Any?>,
7-
private val instancePair: Pair<KParameter, Any?>?,
8-
private val initializationStatus: Int,
9-
private val initializeMask: List<Int>
10-
) {
11-
private val completionValue: Int = initializeMask.reduce { l, r -> l or r }
12-
private val bucketSize = bucket.size
13-
14-
fun generate(): ArgumentBucket {
15-
val tempMap = HashMap<KParameter, Any?>(bucketSize, 1.0f)
16-
instancePair?.run { tempMap[this.first] = this.second }
17-
18-
return ArgumentBucket(
19-
bucket.copyOf(),
20-
tempMap,
21-
initializationStatus,
22-
initializeMask,
23-
completionValue
24-
)
25-
}
26-
}
27-
285
class ArgumentBucket internal constructor(
29-
internal val bucket: Array<Any?>,
30-
internal val bucketMap: MutableMap<KParameter, Any?>,
6+
private val keyArray: Array<KParameter?>,
7+
internal val valueArray: Array<Any?>,
318
private var initializationStatus: Int,
329
private val initializeMask: List<Int>,
3310
private val completionValue: Int
34-
) {
11+
) : Map<KParameter, Any?> {
12+
// インスタンス有りなら1、そうでなければ0スタート
13+
private var count: Int = initializationStatus
14+
3515
val isInitialized: Boolean get() = initializationStatus == completionValue
3616

37-
fun setArgument(kParameter: KParameter, argument: Any?) {
38-
val index = kParameter.index
17+
class MutableEntry internal constructor(
18+
override val key: KParameter,
19+
override var value: Any?
20+
) : Map.Entry<KParameter, Any?>
21+
22+
override val size: Int get() = count
23+
24+
override fun containsKey(key: KParameter): Boolean {
25+
// NOTE: もしかしたらステータスを見た方が速いかも
26+
return keyArray[key.index] != null
27+
}
28+
29+
override fun containsValue(value: Any?): Boolean {
30+
throw UnsupportedOperationException()
31+
}
32+
33+
override fun get(key: KParameter): Any? = valueArray[key.index]
34+
fun getByIndex(key: Int): Any? =
35+
if (initializationStatus and initializeMask[key] != 0) valueArray[key]
36+
else throw IllegalStateException("This argument is not initialized.")
37+
38+
override fun isEmpty(): Boolean = count == 0
39+
40+
override val entries: Set<Map.Entry<KParameter, Any?>>
41+
get() = keyArray.mapNotNull { it?.let { MutableEntry(it, valueArray[it.index]) } }.toSet()
42+
override val keys: MutableSet<KParameter>
43+
get() = keyArray.filterNotNull().toMutableSet()
44+
override val values: MutableCollection<Any?>
45+
get() = throw UnsupportedOperationException()
46+
47+
fun putIfAbsent(key: KParameter, value: Any?) {
48+
val index = key.index
3949
val temp = initializationStatus or initializeMask[index]
4050

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

44-
bucketMap[kParameter] = argument
45-
bucket[index] = argument
54+
count += 1
4655
initializationStatus = temp
56+
keyArray[index] = key
57+
valueArray[index] = value
58+
59+
return
4760
}
4861
}

src/main/kotlin/com/mapk/core/KFunctionForCall.kt

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,16 @@ class KFunctionForCall<T>(private val function: KFunction<T>, instance: Any? = n
1515
// この関数には確実にアクセスするためアクセシビリティ書き換え
1616
function.isAccessible = true
1717

18-
// 初期化処理の共通化のため先に初期化
19-
val tempArray = Array<Any?>(parameters.size) { null }
20-
val maskList = generateSequence(1) { it.shl(1) }.take(parameters.size).toList()
21-
2218
generator = if (instance != null) {
23-
tempArray[0] = instance
24-
25-
// 引数の1番目は初期化済みということでinitializationStatusは1スタート
26-
BucketGenerator(tempArray, parameters.first { it.kind == KParameter.Kind.INSTANCE } to instance, 1, maskList)
19+
BucketGenerator(parameters.size, parameters.first { it.kind == KParameter.Kind.INSTANCE } to instance)
2720
} else {
28-
BucketGenerator(tempArray, null, 0, maskList)
21+
BucketGenerator(parameters.size, null)
2922
}
3023
}
3124

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

3427
fun call(argumentBucket: ArgumentBucket): T =
35-
if (argumentBucket.isInitialized) function.call(*argumentBucket.bucket)
36-
else function.callBy(argumentBucket.bucketMap)
28+
if (argumentBucket.isInitialized) function.call(*argumentBucket.valueArray)
29+
else function.callBy(argumentBucket)
3730
}

src/test/kotlin/com/mapk/core/ArgumentBucketTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ArgumentBucketTest {
3636
@DisplayName("初期化後")
3737
fun isInitialized() {
3838
::sampleFunction.parameters.forEach {
39-
argumentBucket.setArgument(it, object {})
39+
argumentBucket.putIfAbsent(it, object {})
4040
}
4141

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

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

62-
argumentBucket.setArgument(parameter, "first")
63-
argumentBucket.setArgument(parameter, "second")
64-
assertEquals("first", argumentBucket.bucket[0])
62+
argumentBucket.putIfAbsent(parameter, "first")
63+
argumentBucket.putIfAbsent(parameter, "second")
64+
assertEquals("first", argumentBucket.getByIndex(0))
6565
}
6666
}
6767
}

src/test/kotlin/com/mapk/core/KFunctionForCallTest.kt

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.mapk.core
22

3+
import io.mockk.spyk
4+
import io.mockk.verify
35
import kotlin.reflect.full.functions
46
import org.junit.jupiter.api.Assertions.assertEquals
57
import org.junit.jupiter.api.DisplayName
@@ -42,29 +44,55 @@ class KFunctionForCallTest {
4244
@Test
4345
@DisplayName("コンパニオンオブジェクトから取得した場合")
4446
fun fromCompanionObject() {
45-
val function =
46-
Companion::class.functions.find { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }!!
47+
val function = Companion::class.functions
48+
.first { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }
49+
.let { spyk(it) }
4750

4851
val kFunctionForCall = KFunctionForCall(function, Companion)
4952

5053
val bucket = kFunctionForCall.getArgumentBucket()
51-
kFunctionForCall.parameters.forEach { bucket.setArgument(it, it.index) }
54+
kFunctionForCall.parameters.forEach { bucket.putIfAbsent(it, it.index) }
5255
val result = kFunctionForCall.call(bucket)
5356
assertEquals("12", result)
57+
verify(exactly = 1) { function.call(*anyVararg()) }
5458
}
5559

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

5862
@Test
5963
@DisplayName("デフォルト値を用いる場合")
6064
fun useDefaultValue() {
61-
val kFunctionForCall = KFunctionForCall(::func)
65+
val func = spyk(::func)
66+
val kFunctionForCall = KFunctionForCall(func)
6267
val argumentBucket = kFunctionForCall.getArgumentBucket()
6368

64-
::func.parameters.forEach { if (!it.isOptional) argumentBucket.setArgument(it, it.name) }
69+
func.parameters.forEach { if (!it.isOptional) argumentBucket.putIfAbsent(it, it.name) }
6570

6671
val result = kFunctionForCall.call(argumentBucket)
6772
assertEquals("key" to "default", result)
73+
verify(exactly = 1) { func.callBy(any()) }
74+
}
75+
76+
@Test
77+
@DisplayName("同一関数を違うソースから複数回呼んだ場合")
78+
fun multipleCall() {
79+
val function = Companion::class.functions
80+
.first { it.name == (KFunctionForCallTest)::declaredOnCompanionObject.name }
81+
.let { spyk(it) }
82+
83+
val kFunctionForCall = KFunctionForCall(function, Companion)
84+
85+
val bucket1 = kFunctionForCall.getArgumentBucket()
86+
kFunctionForCall.parameters.forEach { bucket1.putIfAbsent(it, it.index) }
87+
val result1 = kFunctionForCall.call(bucket1)
88+
assertEquals("12", result1)
89+
90+
val bucket2 = kFunctionForCall.getArgumentBucket()
91+
kFunctionForCall.parameters.forEach { bucket2.putIfAbsent(it, it.index + 1) }
92+
val result2 = kFunctionForCall.call(bucket2)
93+
assertEquals("23", result2)
94+
95+
verify(exactly = 2) { function.call(*anyVararg()) }
6896
}
6997
}
7098

0 commit comments

Comments
 (0)