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
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = "com.mapk"
version = "0.25"
version = "0.26"

java {
sourceCompatibility = JavaVersion.VERSION_1_8
Expand All @@ -30,7 +30,7 @@ repositories {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(kotlin("reflect"))
api("com.github.ProjectMapK:Shared:0.11")
api("com.github.ProjectMapK:Shared:0.12")

// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.2") {
Expand Down
36 changes: 15 additions & 21 deletions src/main/kotlin/com/mapk/kmapper/BoundKMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ package com.mapk.kmapper

import com.mapk.annotations.KGetterAlias
import com.mapk.annotations.KGetterIgnore
import com.mapk.core.ArgumentBucket
import com.mapk.core.KFunctionForCall
import com.mapk.core.getAliasOrName
import com.mapk.core.isUseDefaultArgument
import com.mapk.core.toKConstructor
import java.lang.IllegalArgumentException
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.KVisibility
import kotlin.reflect.full.findAnnotation
Expand All @@ -23,47 +19,45 @@ class BoundKMapper<S : Any, D : Any> private constructor(
parameterNameConverter: (String) -> String
) {
constructor(function: KFunction<D>, src: KClass<S>, parameterNameConverter: (String) -> String = { it }) : this(
KFunctionForCall(function), src, parameterNameConverter
KFunctionForCall(function, parameterNameConverter), src, parameterNameConverter
)

constructor(clazz: KClass<D>, src: KClass<S>, parameterNameConverter: (String) -> String = { it }) : this(
clazz.toKConstructor(), src, parameterNameConverter
clazz.toKConstructor(parameterNameConverter), src, parameterNameConverter
)

private val parameters: List<BoundParameterForMap<S>>

init {
val srcPropertiesMap: Map<String, KProperty1<S, *>> =
src.memberProperties
.filter {
// アクセス可能かつignoreされてないもののみ抽出
!(it.visibility != KVisibility.PUBLIC) &&
it.getter.annotations.none { annotation -> annotation is KGetterIgnore }
}.associateBy { it.getter.findAnnotation<KGetterAlias>()?.value ?: it.name }

parameters = function.parameters
.filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() }
val srcPropertiesMap: Map<String, KProperty1<S, *>> = src.memberProperties
.filter {
// アクセス可能かつignoreされてないもののみ抽出
!(it.visibility != KVisibility.PUBLIC) &&
it.getter.annotations.none { annotation -> annotation is KGetterIgnore }
}.associateBy { it.getter.findAnnotation<KGetterAlias>()?.value ?: it.name }

parameters = function.requiredParameters
.mapNotNull {
val temp = srcPropertiesMap[parameterNameConverter(it.getAliasOrName()!!)]?.let { property ->
val temp = srcPropertiesMap[it.name]?.let { property ->
BoundParameterForMap.newInstance(it, property, parameterNameConverter)
}

// 必須引数に対応するプロパティがsrcに定義されていない場合エラー
if (temp == null && !it.isOptional) {
throw IllegalArgumentException("Property ${it.name!!} is not declared in ${src.jvmName}.")
throw IllegalArgumentException("Property ${it.name} is not declared in ${src.jvmName}.")
}

temp
}
}

fun map(src: S): D {
val bucket: ArgumentBucket = function.getArgumentBucket()
val adaptor = function.getArgumentAdaptor()

parameters.forEach {
bucket.putIfAbsent(it.param, it.map(src))
adaptor.putIfAbsent(it.name, it.map(src))
}

return function.call(bucket)
return function.call(adaptor)
}
}
32 changes: 16 additions & 16 deletions src/main/kotlin/com/mapk/kmapper/BoundParameterForMap.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
package com.mapk.kmapper

import com.mapk.core.EnumMapper
import com.mapk.core.ValueParameter
import java.lang.IllegalArgumentException
import java.lang.reflect.Method
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.javaGetter

@Suppress("UNCHECKED_CAST")
internal sealed class BoundParameterForMap<S> {
abstract val param: KParameter
abstract val name: String
protected abstract val propertyGetter: Method

abstract fun map(src: S): Any?

private class Plain<S : Any>(
override val param: KParameter,
override val name: String,
override val propertyGetter: Method
) : BoundParameterForMap<S>() {
override fun map(src: S): Any? = propertyGetter.invoke(src)
}

private class UseConverter<S : Any>(
override val param: KParameter,
override val name: String,
override val propertyGetter: Method,
private val converter: KFunction<*>
) : BoundParameterForMap<S>() {
override fun map(src: S): Any? = converter.call(propertyGetter.invoke(src))
}

private class UseKMapper<S : Any>(
override val param: KParameter,
override val name: String,
override val propertyGetter: Method,
private val kMapper: KMapper<*>
) : BoundParameterForMap<S>() {
Expand All @@ -42,31 +42,31 @@ internal sealed class BoundParameterForMap<S> {
}

private class UseBoundKMapper<S : Any, T : Any>(
override val param: KParameter,
override val name: String,
override val propertyGetter: Method,
private val boundKMapper: BoundKMapper<T, *>
) : BoundParameterForMap<S>() {
override fun map(src: S): Any? = boundKMapper.map(propertyGetter.invoke(src) as T)
}

private class ToEnum<S : Any>(
override val param: KParameter,
override val name: String,
override val propertyGetter: Method,
private val paramClazz: Class<*>
) : BoundParameterForMap<S>() {
override fun map(src: S): Any? = EnumMapper.getEnum(paramClazz, propertyGetter.invoke(src) as String)
}

private class ToString<S : Any>(
override val param: KParameter,
override val name: String,
override val propertyGetter: Method
) : BoundParameterForMap<S>() {
override fun map(src: S): String? = propertyGetter.invoke(src).toString()
}

companion object {
fun <S : Any> newInstance(
param: KParameter,
param: ValueParameter<*>,
property: KProperty1<S, *>,
parameterNameConverter: (String) -> String
): BoundParameterForMap<S> {
Expand All @@ -75,7 +75,7 @@ internal sealed class BoundParameterForMap<S> {
?: throw IllegalArgumentException("${property.name} does not have getter.")
propertyGetter.isAccessible = true

val paramClazz = param.type.classifier as KClass<*>
val paramClazz = param.requiredClazz
val propertyClazz = property.returnType.classifier as KClass<*>

// コンバータが取れた場合
Expand All @@ -86,25 +86,25 @@ internal sealed class BoundParameterForMap<S> {

it.singleOrNull()?.second
}?.let {
return UseConverter(param, propertyGetter, it)
return UseConverter(param.name, propertyGetter, it)
}

if (paramClazz.isSubclassOf(propertyClazz)) {
return Plain(param, propertyGetter)
return Plain(param.name, propertyGetter)
}

val javaClazz = paramClazz.java

return when {
javaClazz.isEnum && propertyClazz == String::class -> ToEnum(param, propertyGetter, javaClazz)
paramClazz == String::class -> ToString(param, propertyGetter)
javaClazz.isEnum && propertyClazz == String::class -> ToEnum(param.name, propertyGetter, javaClazz)
paramClazz == String::class -> ToString(param.name, propertyGetter)
// SrcがMapやPairならKMapperを使わないとマップできない
propertyClazz.isSubclassOf(Map::class) || propertyClazz.isSubclassOf(Pair::class) -> UseKMapper(
param, propertyGetter, KMapper(paramClazz, parameterNameConverter)
param.name, propertyGetter, KMapper(paramClazz, parameterNameConverter)
)
// 何にも当てはまらなければBoundKMapperでマップを試みる
else -> UseBoundKMapper(
param, propertyGetter, BoundKMapper(paramClazz, propertyClazz, parameterNameConverter)
param.name, propertyGetter, BoundKMapper(paramClazz, propertyClazz, parameterNameConverter)
)
}
}
Expand Down
71 changes: 34 additions & 37 deletions src/main/kotlin/com/mapk/kmapper/KMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ package com.mapk.kmapper

import com.mapk.annotations.KGetterAlias
import com.mapk.annotations.KGetterIgnore
import com.mapk.core.ArgumentBucket
import com.mapk.core.ArgumentAdaptor
import com.mapk.core.KFunctionForCall
import com.mapk.core.getAliasOrName
import com.mapk.core.isUseDefaultArgument
import com.mapk.core.toKConstructor
import java.lang.reflect.Method
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KVisibility
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaGetter
Expand All @@ -22,28 +19,26 @@ class KMapper<T : Any> private constructor(
parameterNameConverter: (String) -> String
) {
constructor(function: KFunction<T>, parameterNameConverter: (String) -> String = { it }) : this(
KFunctionForCall(function), parameterNameConverter
KFunctionForCall(function, parameterNameConverter), parameterNameConverter
)

constructor(clazz: KClass<T>, parameterNameConverter: (String) -> String = { it }) : this(
clazz.toKConstructor(), parameterNameConverter
clazz.toKConstructor(parameterNameConverter), parameterNameConverter
)

private val parameterMap: Map<String, ParameterForMap<*>> = function.parameters
.filter { it.kind != KParameter.Kind.INSTANCE && !it.isUseDefaultArgument() }
.associate {
(parameterNameConverter(it.getAliasOrName()!!)) to ParameterForMap.newInstance(it, parameterNameConverter)
}
private val parameterMap: Map<String, ParameterForMap<*>> = function.requiredParameters.associate {
it.name to ParameterForMap(it, parameterNameConverter)
}

private val getCache: ConcurrentMap<KClass<*>, List<ArgumentBinder>> = ConcurrentHashMap()

private fun bindArguments(argumentBucket: ArgumentBucket, src: Any) {
private fun bindArguments(argumentAdaptor: ArgumentAdaptor, src: Any) {
val clazz = src::class

// キャッシュヒットしたら登録した内容に沿って取得処理を行う
getCache[clazz]?.let { getters ->
// 取得対象フィールドは十分絞り込んでいると考えられるため、終了判定は行わない
getters.forEach { it.bindArgument(src, argumentBucket) }
getters.forEach { it.bindArgument(src, argumentAdaptor) }
return
}

Expand All @@ -68,73 +63,75 @@ class KMapper<T : Any> private constructor(

val binder = ArgumentBinder(param, javaGetter)

binder.bindArgument(src, argumentBucket)
binder.bindArgument(src, argumentAdaptor)
tempBinderArrayList.add(binder)
// キャッシュの整合性を保つため、ここでは終了判定を行わない
}
}
getCache.putIfAbsent(clazz, tempBinderArrayList)
}

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

private fun bindArguments(argumentBucket: ArgumentBucket, srcPair: Pair<*, *>) {
parameterMap[srcPair.first.toString()]?.let {
argumentBucket.putIfAbsent(it.param, srcPair.second?.let { value -> it.mapObject(value) })
private fun bindArguments(argumentAdaptor: ArgumentAdaptor, srcPair: Pair<*, *>) {
val key = srcPair.first.toString()

parameterMap[key]?.let {
argumentAdaptor.putIfAbsent(key, srcPair.second?.let { value -> it.mapObject(value) })
}
}

fun map(srcMap: Map<String, Any?>): T {
val bucket: ArgumentBucket = function.getArgumentBucket()
bindArguments(bucket, srcMap)
val adaptor: ArgumentAdaptor = function.getArgumentAdaptor()
bindArguments(adaptor, srcMap)

return function.call(bucket)
return function.call(adaptor)
}

fun map(srcPair: Pair<String, Any?>): T {
val bucket: ArgumentBucket = function.getArgumentBucket()
bindArguments(bucket, srcPair)
val adaptor: ArgumentAdaptor = function.getArgumentAdaptor()
bindArguments(adaptor, srcPair)

return function.call(bucket)
return function.call(adaptor)
}

fun map(src: Any): T {
val bucket: ArgumentBucket = function.getArgumentBucket()
bindArguments(bucket, src)
val adaptor: ArgumentAdaptor = function.getArgumentAdaptor()
bindArguments(adaptor, src)

return function.call(bucket)
return function.call(adaptor)
}

fun map(vararg args: Any): T {
val bucket: ArgumentBucket = function.getArgumentBucket()
val adaptor: ArgumentAdaptor = function.getArgumentAdaptor()

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

return function.call(bucket)
return function.call(adaptor)
}
}

private class ArgumentBinder(private val param: ParameterForMap<*>, private val javaGetter: Method) {
fun bindArgument(src: Any, bucket: ArgumentBucket) {
fun bindArgument(src: Any, adaptor: ArgumentAdaptor) {
// 初期化済みであれば高コストな取得処理は行わない
if (!bucket.containsKey(param.param)) {
if (!adaptor.isInitialized(param.name)) {
// javaGetterを呼び出す方が高速
bucket.putIfAbsent(param.param, javaGetter.invoke(src)?.let { param.mapObject(it) })
adaptor.putIfAbsent(param.name, javaGetter.invoke(src)?.let { param.mapObject(it) })
}
}
}
Loading