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
Show all changes
27 commits
Select commit Hold shift + click to select a range
9660bfd
findAnnotationを使って書き直し
k163377 Feb 22, 2020
0e6a895
シンプル化
k163377 Feb 22, 2020
10c0c6a
パラメータのmap(仮置き)を追加
k163377 Feb 22, 2020
108374c
parameterMapを用いる形に修正
k163377 Feb 22, 2020
3cd5cb1
parameterMapを用いる形に修正
k163377 Feb 22, 2020
1441f23
parameterMapを用いる形に修正
k163377 Feb 22, 2020
b4d7035
parameterMapを用いる形に修正
k163377 Feb 22, 2020
e1f3179
利用しなくなったため削除
k163377 Feb 22, 2020
3c809d1
nameはmapに置く形に修正
k163377 Feb 22, 2020
c5112f1
整形
k163377 Feb 22, 2020
535a190
呼び出し高速化用にクラスを追加
k163377 Feb 22, 2020
7decb7a
callに切り替えて高速化
k163377 Feb 22, 2020
b1bdacc
コンストラクタの取り方を修正し若干高速化
k163377 Feb 23, 2020
4c626b1
責務を整理し、companion objectが入った場合に対応できるよう調整
k163377 Feb 23, 2020
6805eeb
方式変更に合わせテストを修正
k163377 Feb 23, 2020
4d27b4d
エイリアス有りでのテストを追加
k163377 Feb 23, 2020
41487ba
メソッド内ではindexしか利用しなくなったため修正
k163377 Feb 23, 2020
9569f7f
プロパティとゲッターのaliasはそれぞれ別アノテーションで扱うように修正
k163377 Feb 23, 2020
2eca20f
テスト修正
k163377 Feb 23, 2020
364e4d0
クラスから呼び出す際はjavaGetterを使うように修正
k163377 Feb 23, 2020
0c81260
不要になったため削除
k163377 Feb 23, 2020
6d79fb3
処理の共通化
k163377 Feb 23, 2020
930877d
不要になった処理を削除
k163377 Feb 23, 2020
2a92908
処理の共通化
k163377 Feb 23, 2020
79500bc
処理の共通化
k163377 Feb 23, 2020
fcac5f5
処理の共通化
k163377 Feb 23, 2020
034da25
readmeを修正
k163377 Feb 23, 2020
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
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