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: 3 additions & 1 deletion 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.1"
version = "0.6"

java {
sourceCompatibility = JavaVersion.VERSION_1_8
Expand All @@ -29,6 +29,8 @@ repositories {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(kotlin("reflect"))
// https://mvnrepository.com/artifact/org.jetbrains/annotations
implementation(group = "org.jetbrains", name = "annotations", version = "19.0.0")

// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.0") {
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/mapk/core/BucketGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

import kotlin.Pair;
import kotlin.reflect.KParameter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

class BucketGenerator {
private final int initializationStatus;
@NotNull
private final List<Integer> initializeMask;
private final int completionValue;

@NotNull
private final KParameter[] keyArray;
@NotNull
private final Object[] valueArray;

BucketGenerator(int capacity, Pair<KParameter, Object> instancePair) {
BucketGenerator(int capacity, @Nullable Pair<KParameter, Object> instancePair) {
keyArray = new KParameter[capacity];
valueArray = new Object[capacity];

Expand All @@ -38,6 +43,7 @@ class BucketGenerator {
this.completionValue = completionValue;
}

@NotNull
ArgumentBucket generate() {
return new ArgumentBucket(
keyArray.clone(),
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/mapk/core/EnumMapper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.mapk.core;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class EnumMapper {
/**
* Kotlinの型推論バグでクラスからvalueOfが使えないため、ここだけJavaで書いている(型引数もT extends Enumでは書けなかった)
Expand All @@ -8,8 +11,9 @@ public class EnumMapper {
* @param <T> enumClass
* @return Enum.valueOf
*/
@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> T getEnum(Class<T> clazz, String value) {
public static <T> T getEnum(@NotNull Class<T> clazz, @Nullable String value) {
if (value == null || value.isEmpty()) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package com.mapk.annotations
@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class KConverter
annotation class KConstructor
7 changes: 3 additions & 4 deletions src/main/kotlin/com/mapk/core/ArgumentBucket.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mapk.core

import java.util.Objects
import kotlin.reflect.KParameter

class ArgumentBucket internal constructor(
Expand All @@ -26,9 +27,7 @@ class ArgumentBucket internal constructor(
return keyArray[key.index] != null
}

override fun containsValue(value: Any?): Boolean {
throw UnsupportedOperationException()
}
override fun containsValue(value: Any?): Boolean = valueArray.any { Objects.equals(value, it) }

override fun get(key: KParameter): Any? = valueArray[key.index]
fun getByIndex(key: Int): Any? =
Expand All @@ -42,7 +41,7 @@ class ArgumentBucket internal constructor(
override val keys: MutableSet<KParameter>
get() = keyArray.filterNotNull().toMutableSet()
override val values: MutableCollection<Any?>
get() = throw UnsupportedOperationException()
get() = valueArray.filterIndexed { i, _ -> initializationStatus and initializeMask[i] != 0 }.toMutableList()

fun putIfAbsent(key: KParameter, value: Any?) {
val index = key.index
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/com/mapk/core/Functions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mapk.core

import com.mapk.annotations.KParameterAlias
import kotlin.reflect.KParameter
import kotlin.reflect.full.findAnnotation

/**
* パラメータからエイリアスもしくはプロパティ名を取得する関数
*/
fun KParameter.getAliasOrName(): String? = findAnnotation<KParameterAlias>()?.value ?: name
27 changes: 26 additions & 1 deletion src/main/kotlin/com/mapk/core/KFunctionForCall.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.mapk.core

import com.mapk.annotations.KConstructor
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.functions
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible

class KFunctionForCall<T>(private val function: KFunction<T>, instance: Any? = null) {
class KFunctionForCall<T>(internal val function: KFunction<T>, instance: Any? = null) {
val parameters: List<KParameter> = function.parameters
private val generator: BucketGenerator

Expand All @@ -28,3 +33,23 @@ class KFunctionForCall<T>(private val function: KFunction<T>, instance: Any? = n
if (argumentBucket.isInitialized) function.call(*argumentBucket.valueArray)
else function.callBy(argumentBucket)
}

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

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

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

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

throw IllegalArgumentException("Find multiple target.")
}
68 changes: 68 additions & 0 deletions src/test/kotlin/com/mapk/core/ToKConstructorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.mapk.core

import com.mapk.annotations.KConstructor
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
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

@Suppress("UNCHECKED_CAST", "unused")
@DisplayName("クラスからのコンストラクタ抽出関連テスト")
class ToKConstructorTest {
private class SecondaryConstructorDst(val argument: Int) {
@KConstructor
constructor(argument: Number) : this(argument.toInt())
}

class CompanionFactoryDst(val argument: IntArray) {
companion object {
@KConstructor
fun factory(csv: String): CompanionFactoryDst {
return csv.split(",").map { it.toInt() }.toIntArray().let { CompanionFactoryDst(it) }
}
}
}
private class ConstructorDst(val argument: String)
class MultipleConstructorDst @KConstructor constructor(val argument: Int) {
@KConstructor
constructor(argument: String) : this(argument.toInt())
}

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 = SecondaryConstructorDst::class.toKConstructor().function
Assertions.assertTrue(function.annotations.any { it is KConstructor })
}

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

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

@Test
@DisplayName("対象を複数指定した場合のテスト")
fun testMultipleDeclareConstructor() {
assertThrows<IllegalArgumentException> { MultipleConstructorDst::class.toKConstructor() }
}
}