Skip to content

Commit

Permalink
Add static factories for KotlinTypeToken and more accessors
Browse files Browse the repository at this point in the history
  • Loading branch information
freya022 committed Feb 16, 2025
1 parent a4daef6 commit 93d9225
Showing 1 changed file with 81 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package io.github.freya022.botcommands.api.core.reflect

import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.createType
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmErasure

/**
Expand All @@ -19,21 +25,89 @@ import kotlin.reflect.jvm.jvmErasure
* var typeToken = new KotlinTypeToken<T>() {};
* }
* ```
*
* In other cases, you can create instances for any type using the static factory methods.
*/
open class KotlinTypeToken<T> protected constructor() {
open class KotlinTypeToken<T> {

/**
* The Kotlin type represented by this instance.
*/
val type: KType = this::class.supertypes
// `this` is a subclass of this class, so there must be a supertype of our erasure
.single { it.jvmErasure == KotlinTypeToken::class }
// Get the T type of KotlinTypeToken
.arguments[0].type!!
val type: KType

/**
* The Java type represented by this instance.
*/
val javaType: Type get() = type.javaType

/**
* The [KClass] instance representing the runtime class to which this type is erased to.
*/
val kotlinErasure: KClass<*> get() = type.jvmErasure

/**
* The [Class] instance representing the runtime class to which this type is erased to.
*/
val javaErasure: Class<*> get() = type.jvmErasure.java

protected constructor() {
this.type = this::class.supertypes
// `this` is a subclass of this class, so there must be a supertype of our erasure
.single { it.jvmErasure == KotlinTypeToken::class }
// Get the T type of KotlinTypeToken
.arguments[0].type!!

init {
requireNotNull(type.classifier) {
"Cannot represent '${(this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]}' as a KotlinTypeToken"
}
}

private constructor(type: KType) {
this.type = type
}

companion object {

/**
* Creates a [KotlinTypeToken] constructed by a [clazz], followed by its type arguments.
*
* This is typically needed when the type token cannot be created at compile time,
* where instead, you would be creating a subclass of [KotlinTypeToken].
*
* ### Example
* ```java
* public static <V> KotlinTypeToken<Map<String, V>> createMapTypeToken(Class<V> valueType) {
* return (KotlinTypeToken<Map<String, V>>) KotlinTypeToken.ofClass(Map.class, List.of(String.class, valueType));
* }
* ```
*
* As you can see, this cannot guarantee type safety,
* you must take care of passing the right number of arguments.
*/
@JvmStatic
@JvmOverloads
fun ofClass(clazz: Class<*>, arguments: List<KotlinTypeToken<*>> = listOf()): KotlinTypeToken<*> =
KotlinTypeToken<Any>(
clazz.kotlin.createType(
arguments.map { KTypeProjection.invariant(it.type) },
false,
listOf()
)
)

/**
* Creates a [KotlinTypeToken] with the given [KClass].
*
* If the given class has type parameters, they will be replaced by star projections (`*`),
* this is equivalent to [`clazz.startProjectedType`][KClass.starProjectedType].
*/
@JvmStatic
fun ofKClass(clazz: KClass<*>) = KotlinTypeToken<Any>(clazz.starProjectedType)

/**
* Creates a [KotlinTypeToken] with the given [KType], unchanged.
*/
@JvmStatic
fun ofType(type: KType) = KotlinTypeToken<Any>(type)
}
}

0 comments on commit 93d9225

Please sign in to comment.