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

Commit

Permalink
Merge pull request #4 from k163377/feature
Browse files Browse the repository at this point in the history
 Improve processing time.
  • Loading branch information
k163377 authored Feb 23, 2020
2 parents c4bae22 + 034da25 commit c3b2be5
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 103 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Dst(
### Set alias on map
#### for getter
```kotlin
class Src(@get:PropertyAlias("aliased") val str: String)
class Src(@KGetterAlias("aliased") val str: String)

class Dst(val aliased: String)
```
Expand All @@ -53,7 +53,7 @@ class Dst(val aliased: String)
```kotlin
class Src(val str: String)

class Dst(@param:PropertyAlias("str") private val _src: String) {
class Dst(@param:KPropertyAlias("str") private val _src: String) {
val src = _src.someArrangement
}
```
Expand All @@ -64,15 +64,15 @@ val srcMap = mapOf("snake_case" to "SnakeCase")

class Dst(val snakeCase: String)

val dst: Dst = Mapper(DataClass::class.primaryConstructor!!) { it.toSnakeCase }.map(src)
val dst: Dst = Mapper(::DataClass) { it.toSnakeCase }.map(src)
```

### Map param to another class

```kotlin
class CreatorClass @SingleArgCreator constructor(val arg: String) {
companion object {
@SingleArgCreator
@KConverter
fun fromInt(arg: Int): CreatorClass {
return CreatorClass(arg.toString)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wrongwrong.mapk.annotations

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class KGetterAlias(val value: String)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.wrongwrong.mapk.annotations

@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY_GETTER)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class KPropertyAlias(val value: String)
25 changes: 25 additions & 0 deletions src/main/kotlin/com/wrongwrong/mapk/core/KFunctionForCall.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.wrongwrong.mapk.core

import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
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()

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

fun call(arguments: Array<Any?>): T {
return function.call(*arguments)
}
}
150 changes: 70 additions & 80 deletions src/main/kotlin/com/wrongwrong/mapk/core/KMapper.kt
Original file line number Diff line number Diff line change
@@ -1,132 +1,122 @@
package com.wrongwrong.mapk.core

import com.wrongwrong.mapk.annotations.KConstructor
import com.wrongwrong.mapk.annotations.KGetterAlias
import com.wrongwrong.mapk.annotations.KPropertyAlias
import com.wrongwrong.mapk.annotations.KPropertyIgnore
import java.lang.reflect.Method
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty1
import kotlin.reflect.KParameter
import kotlin.reflect.KVisibility
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions
import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaGetter

class KMapper<T : Any> private constructor(
private val function: KFunctionForCall<T>,
propertyNameConverter: (String) -> String = { it }
) {
constructor(function: KFunction<T>, propertyNameConverter: (String) -> String = { it }) : this(
KFunctionForCall(function), propertyNameConverter
)

class KMapper<T : Any>(private val function: KFunction<T>, propertyNameConverter: (String) -> String = { it }) {
constructor(clazz: KClass<T>, propertyNameConverter: (String) -> String = { it }) : this(
getTarget(clazz), propertyNameConverter
)

private val parameters: Set<ParameterForMap<*>> = function.parameters
.map { ParameterForMap.newInstance(it, propertyNameConverter) }
.toSet()
private val parameterMap: Map<String, ParameterForMap<*>> = function.parameters
.filter { it.kind != KParameter.Kind.INSTANCE }
.associate {
(it.findAnnotation<KPropertyAlias>()?.value ?: propertyNameConverter(it.name!!)) to
ParameterForMap.newInstance(it)
}

init {
if (parameters.isEmpty()) throw IllegalArgumentException("This function is not require arguments.")

// private関数に対してもマッピングできなければ何かと不都合があるため、accessibleは書き換える
function.isAccessible = true
if (parameterMap.isEmpty()) throw IllegalArgumentException("This function is not require arguments.")
}

fun map(srcMap: Map<String, Any?>): T {
return parameters.associate {
// 取得した内容がnullでなければ適切にmapする
it.param to srcMap.getValue(it.name)?.let { value ->
mapObject(it, value)
private fun bindParameters(targetArray: Array<Any?>, 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) }
}
}
}.let { function.callBy(it) }
}
}

fun map(srcPair: Pair<String, Any?>): T = parameters
.single { it.name == srcPair.first }
.let {
function.callBy(mapOf(it.param to srcPair.second?.let { value -> mapObject(it, value) }))
private fun bindParameters(targetArray: Array<Any?>, src: Map<*, *>) {
src.forEach { (key, value) ->
parameterMap[key]?.let { param ->
// 取得した内容がnullでなければ適切にmapする
targetArray[param.index] = value?.let { mapObject(param, it) }
}
}
}

fun map(src: Any): T {
val srcMap: Map<String, KProperty1.Getter<*, *>> =
src::class.memberProperties.filterTargets().associate { property ->
val getter = property.getAccessibleGetter()
private fun bindParameters(targetArray: Array<Any?>, srcPair: Pair<*, *>) {
parameterMap.getValue(srcPair.first.toString()).let {
targetArray[it.index] = srcPair.second?.let { value -> mapObject(it, value) }
}
}

val key = getter.annotations
.find { it is KPropertyAlias }
?.let { (it as KPropertyAlias).value }
?: property.name
fun map(srcMap: Map<String, Any?>): T {
val array: Array<Any?> = function.argumentArray
bindParameters(array, srcMap)
return function.call(array)
}

key to getter
}
fun map(srcPair: Pair<String, Any?>): T {
val array: Array<Any?> = function.argumentArray
bindParameters(array, srcPair)
return function.call(array)
}

return parameters.associate {
// 取得した内容がnullでなければ適切にmapする
it.param to srcMap.getValue(it.name).call(src)?.let { value ->
mapObject(it, value)
}
}.let { function.callBy(it) }
fun map(src: Any): T {
val array: Array<Any?> = function.argumentArray
bindParameters(array, src)
return function.call(array)
}

fun map(vararg args: Any): T {
val srcMap: Map<String, () -> Any?> = listOf(*args)
.map { arg ->
when (arg) {
is Map<*, *> -> arg.entries.associate { (key, value) ->
(key as String) to { value }
}
is Pair<*, *> -> mapOf(arg.first as String to { arg.second })
else -> {
arg::class.memberProperties.filterTargets().associate { property ->
val getter = property.getAccessibleGetter()

val key = getter.annotations
.find { it is KPropertyAlias }
?.let { (it as KPropertyAlias).value }
?: property.name

key to { getter.call(arg) }
}
}
}
}.reduce { acc, map ->
acc + map
}
val array: Array<Any?> = function.argumentArray

return parameters.associate {
// 取得した内容がnullでなければ適切にmapする
it.param to srcMap.getValue(it.name)()?.let { value ->
mapObject(it, value)
listOf(*args).forEach { arg ->
when (arg) {
is Map<*, *> -> bindParameters(array, arg)
is Pair<*, *> -> bindParameters(array, arg)
else -> bindParameters(array, arg)
}
}.let { function.callBy(it) }
}
}
}

private fun Collection<KProperty1<*, *>>.filterTargets(): Collection<KProperty1<*, *>> {
return filter {
it.visibility == KVisibility.PUBLIC && it.annotations.none { annotation -> annotation is KPropertyIgnore }
return function.call(array)
}
}

private fun KProperty1<*, *>.getAccessibleGetter(): KProperty1.Getter<*, *> {
// アクセス制限の有るクラスではpublicなプロパティでもゲッターにアクセスできない場合が有るため、アクセス可能にして使う
getter.isAccessible = true
return getter
}

@Suppress("UNCHECKED_CAST")
internal fun <T : Any> getTarget(clazz: KClass<T>): KFunction<T> {
val factoryConstructor: List<KFunction<T>> =
internal fun <T : Any> getTarget(clazz: KClass<T>): KFunctionForCall<T> {
val factoryConstructor: List<KFunctionForCall<T>> =
clazz.companionObjectInstance?.let { companionObject ->
companionObject::class.functions
.filter { it.annotations.any { annotation -> annotation is KConstructor } }
.map { KFunctionWithInstance(it, companionObject) as KFunction<T> }
.map { KFunctionForCall(it, companionObject) as KFunctionForCall<T> }
} ?: emptyList()

val constructors: List<KFunction<T>> = factoryConstructor + clazz.constructors
val constructors: List<KFunctionForCall<T>> = factoryConstructor + clazz.constructors
.filter { it.annotations.any { annotation -> annotation is KConstructor } }
.map { KFunctionForCall(it) }

if (constructors.size == 1) return constructors.single()

if (constructors.isEmpty()) return clazz.primaryConstructor!!
if (constructors.isEmpty()) return KFunctionForCall(clazz.primaryConstructor!!)

throw IllegalArgumentException("Find multiple target.")
}
Expand Down
16 changes: 3 additions & 13 deletions src/main/kotlin/com/wrongwrong/mapk/core/ParameterForMap.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.wrongwrong.mapk.core

import com.wrongwrong.mapk.annotations.KConverter
import com.wrongwrong.mapk.annotations.KPropertyAlias
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
Expand All @@ -11,16 +10,7 @@ import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.staticFunctions
import kotlin.reflect.jvm.isAccessible

internal class ParameterForMap<T : Any> private constructor(
val param: KParameter,
val clazz: KClass<T>,
propertyNameConverter: (String) -> String
) {
val name: String = param.annotations
.find { it is KPropertyAlias }
?.let { (it as KPropertyAlias).value }
?: propertyNameConverter(param.name!!)

internal class ParameterForMap<T : Any> private constructor(val index: Int, val clazz: KClass<T>) {
val javaClazz: Class<T> by lazy {
clazz.java
}
Expand All @@ -34,8 +24,8 @@ internal class ParameterForMap<T : Any> private constructor(
creators.find { (key, _) -> input.isSubclassOf(key) }?.second

companion object {
fun newInstance(param: KParameter, propertyNameConverter: (String) -> String): ParameterForMap<*> {
return ParameterForMap(param, param.type.classifier as KClass<*>, propertyNameConverter)
fun newInstance(param: KParameter): ParameterForMap<*> {
return ParameterForMap(param.index, param.type.classifier as KClass<*>)
}
}
}
Expand Down
18 changes: 15 additions & 3 deletions src/test/kotlin/mapk/core/GetTargetTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
package mapk.core

import com.wrongwrong.mapk.annotations.KConstructor
import com.wrongwrong.mapk.core.KFunctionForCall
import com.wrongwrong.mapk.core.getTarget
import kotlin.reflect.KFunction
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
Expand All @@ -27,26 +31,34 @@ class MultipleConstructorDst @KConstructor constructor(val argument: Int) {
@KConstructor constructor(argument: String) : this(argument.toInt())
}

@Suppress("UNCHECKED_CAST")
@DisplayName("クラスからのコンストラクタ抽出関連テスト")
class GetTargetTest {
private fun <T : Any> KFunctionForCall<T>.getTargetFunction(): KFunction<T> {
return this::class.memberProperties.first { it.name == "function" }.getter.let {
it.isAccessible = true
it.call(this) as KFunction<T>
}
}

@Test
@DisplayName("セカンダリコンストラクタからの取得テスト")
fun testGetFromSecondaryConstructor() {
val function = getTarget(SecondaryConstructorDst::class)
val function = getTarget(SecondaryConstructorDst::class).getTargetFunction()
assertTrue(function.annotations.any { it is KConstructor })
}

@Test
@DisplayName("ファクトリーメソッドからの取得テスト")
fun testGetFromFactoryMethod() {
val function = getTarget(CompanionFactoryDst::class)
val function = getTarget(SecondaryConstructorDst::class).getTargetFunction()
assertTrue(function.annotations.any { it is KConstructor })
}

@Test
@DisplayName("無指定でプライマリコンストラクタからの取得テスト")
fun testGetFromPrimaryConstructor() {
val function = getTarget(ConstructorDst::class)
val function = getTarget(ConstructorDst::class).getTargetFunction()
assertEquals(ConstructorDst::class.primaryConstructor, function)
}

Expand Down
Loading

0 comments on commit c3b2be5

Please sign in to comment.