How to get the class through reflection and then pass in the generic? - kotlin

There is such a function
public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
repeatInterval: Duration
): PeriodicWorkRequest.Builder {
return PeriodicWorkRequest.Builder(W::class.java, repeatInterval)
}
requires passing in a generic. How do I set this generic through reflection? The following is the pseudocode.
val work = Class.forName("com.demo.work")
PeriodicWorkRequestBuilder<work::class>(1, TimeUnit.HOURS).build()

Related

How can I get the inner type of a generic class in Kotlin?

I know with Kotlin (on the JVM) I can get the type of a class with foo::class. And I know I can generic information, even at runtime, with typeOf<>() (as long as my function is inline). But I can't figure out how to get Int from a List<Int> for example.
Here's as far as I can get:
import kotlin.reflect.KType
import kotlin.reflect.typeOf
fun main() {
TypeTest.get<Int>()
TypeTest.get<List<Int>>()
TypeTest.get<Map<String, List<Int>>>()
}
class TypeTest {
companion object {
#OptIn(ExperimentalStdlibApi::class)
inline fun <reified OUT : Any> get() {
generateReturnType(typeOf<OUT>())
}
fun generateReturnType(type: KType) {
val classifier = type.classifier!!
println("class $classifier has parameters ${classifier::class.typeParameters}")
}
}
}
This prints:
class class kotlin.Int has parameters [T]
class class kotlin.collections.List has parameters [T]
class class kotlin.collections.Map has parameters [T]
Given a KClassifier, how can I get its inner type (ideally recursively, in the case of a List<List<Int>>)? Or is there another way to do this?

Kotlin: generate a Factory by class

We're trying to do some generic processing in kotlin. Basically, for a given class, we want to get the related Builder object. i.a. for any object that extends a GenericObject, we want a Builder of that Object.
interface Builder<T : GenericObject>
object ConcreteBuilder: Builder<ConcreteObject>
We'd need a function that will return ConcreteBuilder from ConcreteObject
Our current implementation is a Map:
val map = mapOf<KClass<out GenericObject>, Builder<out GenericObject>>(
ConcreteObject::class to ConcreteBuilder
)
Then we can get it with:
inline fun <reified T : GenericObject> transform(...): T {
val builder = map[T::class] as Builder<T>
...
However this isn't very nice as:
we need an explicit cast to Builder<T>
the map has no notion of T, a key and a value could be related to different types.
Is there any better way to achieve it?
A wrapper for the map could be:
class BuilderMap {
private val map = mutableMapOf<KClass<out GenericObject>, Builder<out GenericObject>>()
fun <T: GenericObject> put(key: KClass<T>, value: Builder<T>) {
map[key] = value
}
operator fun <T: GenericObject> get(key: KClass<T>): Builder<T> {
return map[key] as Builder<T>
}
}
This hides the ugliness, while not completely removing it.
To use:
val builderMap = BuilderMap()
builderMap.put(ConcreteObject::class, ConcreteBuilder)
builderMap.put(BetonObject::class, BetonBuilder)
// builderMap.put(BetonObject::class, ConcreteBuilder) – will not compile
val builder = builderMap[T::class]

Pass a Java class reference as a function parameter in Kotlin

I want to pass a reference to a class to a function:
getCallData(ServiceCallBase::class.java)
fun getCallData(msg: Message, t: KClass<Any>): String {
return gson.fromJson((msg.obj as Bundle).getString(SERVICE_BUNDLE_KEY_DATA_TO_SERVICE), t)
}
The t parameter is not correct here. How do I correctly define the t parameter? The closest I can get is:
private inline fun <reified T> getCallData(msg: Message): String {
return gson.fromJson((msg.obj as Bundle).getString(SERVICE_BUNDLE_KEY_DATA_TO_SERVICE), T)
}
But here T is not an expression.
try this
inline fun <reified T> getCallData(msg: Message): String {
return gson.fromJson((msg.obj as Bundle)
.getString(SERVICE_BUNDLE_KEY_DATA_TO_SERVICE), T::class.java)
}
and use it like this :
getCallData<ServiceCallBase>(Message())
The problem is that ServiceCallBase is not a subtype of Any. You should change the signature to:
fun getCallData(t: KClass<*>) { ... }
Then, to call this function just use:
getCallData(ServiceCallBase::class)
But, to do that in more kotlin way use the second snipped you pasted:
inline fun <reified T> getCallData() { /* use T::class property here */ }
and call the function like this:
getCallData<ServiceCallBase>()

Type inference only works for extension function

The following code works fine and the call to the foo.get() extension function returns the correct type BarImpl.
open class Bar
class BarImpl: Bar()
class Foo<T : Bar>
inline fun <reified T : Bar> Foo<T>.get(): T {
return SomeMap(this).get(T::class)
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
But when I try to move Foo<T>.get() into the class the type inference fails
class Foo<T : Bar> {
inline fun <reified T : Bar> get(): T {
return SomeMap(this).get(T::class)
}
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
error: type inference failed: Not enough information to infer parameter T in inline fun get(): T
Please specify it explicitly.
val vm = foo.get()
^
How can I move the function into the class?
The extension function returns the result of the Foo type parameter. So the result type can be inferred from the receiver type.
And the member function result type has nothing in common with Foo type parameter except the name, which means nothing for a compiler. You can see that T in method and T in class are different types by writing and compiling the following code:
Foo<BarImpl>().get<BarImpl2>()
If you want to make get to be a member function which returns the result of Foo type parameter, you should remove type parameter from function and inject class instance via the constructor:
class Foo<T : Bar>(private val clazz: KClass<T>) {
fun get(): T {
return SomeMap(this).get(clazz)
}
companion object {
inline operator fun <reified T : Bar> invoke() = Foo(T::class)
}
}

How to Deserialize Jackson container with Generics Generically

I have a signature for a method that looks like this:
inline fun <reified TData: IBulkModel?> bulkCreate(path: String) {
val type = jacksonTypeRef<RequestListWrapper<TData>>()
}
There's more to it, but this is the pertinent portion. I have a refied T here in an inline function. My expectation is that the T here would be the actual T for the function for any given call to this, but it's not, it's IBulkModel.
Is there a way to make this work with Kotlin, or am I stuck passing in the complete class?
Nested type parameters are lost, even in reified parameters. The only type preserved is the top-level one.
Jackson has a solution for this; you can use the type factory from Java:
data class Generic<T>(val t: T)
fun main(args: Array<String>)
{
val mapper = ObjectMapper()
val type: JavaType = mapper.typeFactory
.constructParametricType(Generic::class.java, Int::class.java)
val instance: Generic<Int> = mapper.readValue("""{"t":32}""", type)
}