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
26 changes: 26 additions & 0 deletions src/main/kotlin/com/wrongwrong/mapk/core/ArgumentBucket.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.wrongwrong.mapk.core

class ArgumentBucket(
val bucket: Array<Any?>,
private var initializeStatus: Int,
private val initializeMask: List<Int>,
// clone時の再計算を避けるため1回で済むようにデフォルト値化
private val completionValue: Int = initializeMask.reduce { l, r -> l or r }
) : Cloneable {
val isInitialized: Boolean get() = initializeStatus == completionValue
val notInitializedParameterIndexes: List<Int> get() = initializeMask.indices.filter {
initializeStatus and initializeMask[it] == 0
}

fun setArgument(argument: Any?, index: Int) {
// 先に入ったものを優先するため、初期化済みなら何もしない
if (initializeStatus and initializeMask[index] != 0) return

bucket[index] = argument
initializeStatus = initializeStatus or initializeMask[index]
}

public override fun clone(): ArgumentBucket {
return ArgumentBucket(bucket.copyOf(), initializeStatus, initializeMask, completionValue)
}
}
27 changes: 20 additions & 7 deletions src/main/kotlin/com/wrongwrong/mapk/core/KFunctionForCall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,33 @@ import kotlin.reflect.jvm.isAccessible

class KFunctionForCall<T>(private val function: KFunction<T>, instance: Any? = null) {
val parameters: List<KParameter> = function.parameters
private val originalArray: Array<Any?>
val argumentArray: Array<Any?> get() = originalArray.copyOf()
private val originalArgumentBucket: ArgumentBucket

fun getArgumentBucket(): ArgumentBucket = originalArgumentBucket.clone()

init {
// この関数には確実にアクセスするためアクセシビリティ書き換え
function.isAccessible = true
originalArray = if (instance != null) {
Array(parameters.size) { if (it == 0) instance else null }
originalArgumentBucket = if (instance != null) {
ArgumentBucket(
Array(parameters.size) { if (it == 0) instance else null },
1,
generateSequence(1) { it.shl(1) }
.take(parameters.size)
.toList()
)
} else {
Array(parameters.size) { null }
ArgumentBucket(
Array(parameters.size) { null },
0,
generateSequence(1) { it.shl(1) }
.take(parameters.size)
.toList()
)
}
}

fun call(arguments: Array<Any?>): T {
return function.call(*arguments)
fun call(argumentBucket: ArgumentBucket): T {
return function.call(*argumentBucket.bucket)
}
}
80 changes: 53 additions & 27 deletions src/main/kotlin/com/wrongwrong/mapk/core/KMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,64 +40,90 @@ class KMapper<T : Any> private constructor(
if (parameterMap.isEmpty()) throw IllegalArgumentException("This function is not require arguments.")
}

private fun bindParameters(targetArray: Array<Any?>, src: Any) {
private fun throwExceptionOnNotInitialized(argumentBucket: ArgumentBucket): Nothing {
val notInitializedIndexes = argumentBucket.notInitializedParameterIndexes
function.parameters
.filter { it.index in notInitializedIndexes }
.map { it.name }
.joinToString(", ")
.let {
throw IllegalArgumentException("Not passed arguments: $it")
}
}

private fun bindArguments(argumentBucket: ArgumentBucket, src: Any) {
src::class.memberProperties.forEach { property ->
val javaGetter: Method? = property.javaGetter
if (javaGetter != null && property.visibility == KVisibility.PUBLIC && property.annotations.none { annotation -> annotation is KPropertyIgnore }) {
parameterMap[property.findAnnotation<KGetterAlias>()?.value ?: property.name]?.let {
// javaGetterを呼び出す方が高速
javaGetter.isAccessible = true
targetArray[it.index] = javaGetter.invoke(src)?.let { value -> mapObject(it, value) }
argumentBucket.setArgument(javaGetter.invoke(src)?.let { value -> mapObject(it, value) }, it.index)
// 終了判定
if (argumentBucket.isInitialized) return
}
}
}
}

private fun bindParameters(targetArray: Array<Any?>, src: Map<*, *>) {
private fun bindArguments(argumentBucket: ArgumentBucket, src: Map<*, *>) {
src.forEach { (key, value) ->
parameterMap[key]?.let { param ->
// 取得した内容がnullでなければ適切にmapする
targetArray[param.index] = value?.let { mapObject(param, it) }
argumentBucket.setArgument(value?.let { mapObject(param, it) }, param.index)
// 終了判定
if (argumentBucket.isInitialized) return
}
}
}

private fun bindParameters(targetArray: Array<Any?>, srcPair: Pair<*, *>) {
parameterMap.getValue(srcPair.first.toString()).let {
targetArray[it.index] = srcPair.second?.let { value -> mapObject(it, value) }
private fun bindArguments(argumentBucket: ArgumentBucket, srcPair: Pair<*, *>) {
parameterMap[srcPair.first.toString()]?.let {
argumentBucket.setArgument(srcPair.second?.let { value -> mapObject(it, value) }, it.index)
}
}

fun map(srcMap: Map<String, Any?>): T {
val array: Array<Any?> = function.argumentArray
bindParameters(array, srcMap)
return function.call(array)
val bucket: ArgumentBucket = function.getArgumentBucket()
bindArguments(bucket, srcMap)

if (!bucket.isInitialized) throwExceptionOnNotInitialized(bucket)

return function.call(bucket)
}

fun map(srcPair: Pair<String, Any?>): T {
val array: Array<Any?> = function.argumentArray
bindParameters(array, srcPair)
return function.call(array)
val bucket: ArgumentBucket = function.getArgumentBucket()
bindArguments(bucket, srcPair)

if (!bucket.isInitialized) throwExceptionOnNotInitialized(bucket)

return function.call(bucket)
}

fun map(src: Any): T {
val array: Array<Any?> = function.argumentArray
bindParameters(array, src)
return function.call(array)
val bucket: ArgumentBucket = function.getArgumentBucket()
bindArguments(bucket, src)

if (!bucket.isInitialized) throwExceptionOnNotInitialized(bucket)

return function.call(bucket)
}

fun map(vararg args: Any): T {
val array: Array<Any?> = function.argumentArray
val bucket: ArgumentBucket = function.getArgumentBucket()

listOf(*args).forEach { arg ->
when (arg) {
is Map<*, *> -> bindParameters(array, arg)
is Pair<*, *> -> bindParameters(array, arg)
else -> bindParameters(array, arg)
is Map<*, *> -> bindArguments(bucket, arg)
is Pair<*, *> -> bindArguments(bucket, arg)
else -> bindArguments(bucket, arg)
}
}

return function.call(array)
if (!bucket.isInitialized) throwExceptionOnNotInitialized(bucket)

return function.call(bucket)
}
}

Expand All @@ -123,15 +149,15 @@ internal fun <T : Any> getTarget(clazz: KClass<T>): KFunctionForCall<T> {

private fun <T : Any, R : Any> mapObject(param: ParameterForMap<R>, value: T): Any? {
val valueClazz: KClass<*> = value::class
val creator: KFunction<*>? by lazy {
param.getCreator(valueClazz)
}

// パラメータに対してvalueが代入可能(同じもしくは親クラス)であればそのまま用いる
if (param.clazz.isSuperclassOf(valueClazz)) return value

val creator: KFunction<*>? = param.getCreator(valueClazz)

return when {
// パラメータに対してvalueが代入可能(同じもしくは親クラス)であればそのまま用いる
param.clazz.isSuperclassOf(valueClazz) -> value
// creatorに一致する組み合わせが有れば設定されていればそれを使う
creator != null -> creator!!.call(value)
creator != null -> creator.call(value)
// 要求された値がenumかつ元が文字列ならenum mapperでマップ
param.javaClazz.isEnum && value is String -> EnumMapper.getEnum(param.clazz.java, value)
// 要求されているパラメータがStringならtoStringする
Expand Down