I'm trying to retrieve the instances of my properties, like the example below:
data class DataClass(
val inner: InnerClass
)
data class AnotherDataClass(
val annotatedProperty: String,
val dataClass: DataClass
)
instance = AnotherDataClass("prop", DataClass("anotherprop"))
instance::class.memberProperties.forEach {
// how to retrieve the instance of the properties here?
}
Use kotlin's kClass to access the properties. when you are iterating over the properties you can check the type of property and if it is in fact of type DataClass then you cast it to DataClass and access its values as usual.
var instance = AnotherDataClass("prop", DataClass("AnotherProperty"))
instance.javaClass.kotlin.memberProperties.forEach{
var propertyValue = it.get(instance)
when(propertyValue){
// if propertyValue is of DataClass then
// access its internal fields as you like
is DataClass -> println(propertyValue.inner)
else -> println(propertyValue)
}
}
I was able to solve it in a not-very-beautiful way:
instance::class.memberProperties.forEach {
instance.javaClass.getMethod("get${it.name.capitalize()}").invoke(instance)
}
You can use call here.
import kotlin.reflect.full.memberProperties
data class Member(
val id: Int,
val name: String,
)
fun main(){
val member = Member(1, "kyakya")
println(member.id)
println(member.name)
member::class.memberProperties.forEach {
val call = it.call(member)
println("${it.name} : ${call}")
}
}
Gradle:
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
id("org.jetbrains.kotlin.jvm") version "1.5.0"
}
dependencies {
// Kotlin
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
//
implementation("org.jetbrains.kotlin:kotlin-reflect")
}
Related
I have used the memberExtensionProperties() method, but result collection of the extension properties is empty. The test code is attached. What is the right procedure?
class ExtensionPropertyTest {
class DummyClass{}
val DummyClass.id get() = 99
val DummyClass.name get() = "Joe"
#Test
fun testExtensionProperties() {
val dummyClass = DummyClass()
expect(dummyClass.id).toEqual(99) // OK
val properties = DummyClass::class.memberExtensionProperties
.stream()
.toList()
expect(properties).toHaveSize(2) // Fails due a zero size
}
}
memberExtensionProperties does not return extensions over a class, but its members that are at the same time extensions:
fun main() {
println(DummyClass::class.memberExtensionProperties)
}
class DummyClass {
val String.foo: Int
get() = toInt()
}
It is not that easy if at all possible to find all extensions over a class, because extensions are detached from their receivers and they can be located anywhere in the classpath.
I’m trying to develop a codegen IDEA-Plugin. This plugin should analyze KtClass Inheritance and get all inheritance class full name (like com.example.config.TestConfig)
I have tried to find any useful information by viewing PsiViewer. I find that all
inheritance info of KtClass is stored in KtSuperTypeEntry, and I try my best to get full name of inheritance class.
for class Dest:
data class Dest(
val stringValue: String = "123",
override val stringConfig: String = "123",
override val iConfigStr: String = "123",
val b: B = B(),
val c: List<List<Set<Map<String, out Any?>>>> = listOf(),
val config: Config? = Config()
) : Config()
superTypeListEntry.typeAsUserType.referenceExpression.getReferencedName() -return-> "Config"
superTypeListEntry.importReceiverMembers() -return-> null
Seemingly SuperTypeListEntry just contain inheritance class simple name info.
I also try to find inheritance class full name by KtFile, but there is no idea when inheritance class was imported in this KtFile as wildcards:
fun KtSuperTypeListEntry.getType(ktFile: KtFile): String {
val simpleName = superTypeListEntry.text
// try to find by declared KtClass ...
ktFile.children.filterIsInstance<KtClass>().filter { it.name == simpleName }
// try to find by import ...
ktFile.importDirectives.filter { it.importPath.toString().contains(simpleName) }
// try to find by import wildcards ...
ktFile.importDirectives.filter { it.importPath.toString().endWith("*") }.forEach {
val split = it.importPath.split(".")
split.set(split.size - 1, simpleName)
val maybeFullName = split.joinToString(",") { it }
// confused on how to detect "maybeFullName" is correct ...
}
}
Question
How can I retrieve all inheritance class full name from Kotlin Psi API? Thank you!
After thousand of investigations and debugging, I find that it is possible to find a class's inheritance classes by BindingContext. BindingContext can analyze a TypeReference and find the reference of KotlinType. The code might be like this:
ktClass.superTypeListEntries.map { superTypeEntry ->
val typeReference = superTypeEntry.typeReference
val bindingContext = typeReference.analyze()
bindingContext.get(BindingContext.TYPE, typeReference)
}.forEach { kotlinType ->
val classId = kotlinType.constructor.declarationDescriptor.classId
val packageName = classId.packageFqName
val simpleName = classId.relativeClassName
// It can also get the generics of this class by KotlinType.arguments
val generics = kotlinType.arguments
}
Also, you can get super types full name of the class by KtLightClass, the code might be like this:
val ktLightClass = ktClass.toLightClass()
val superTypesFullName = ktLightClass?.supers?.forEach { superType ->
val packageName = superType.qualifiedName
val simpleName = superType.name
// you can get as KtClass by this, which can help you unify design of interface.
val ktClass = superType.kotlinOrigin
}
I can access a private val value using reflection as below
fun main() {
val mainClass = MainClass()
val f = MainClass::class.memberProperties.find { it.name == "info" }
f?.let {
it.isAccessible = true
val w = it.get(mainClass) as String
println(w)
}
}
class MainClass {
private val info: String = "Hello"
}
But if I want to change info, how could I do it using reflection?
Answer
In short, you have to use Java reflection APIs in this case, and here is how to do it:
fun main() {
val mainClass = MainClass()
val f = MainClass::class.java.getDeclaredField("info")
f.isAccessible = true
f.set(mainClass, "set from reflection")
mainClass.printInfo() // Prints "set from reflection"
}
class MainClass {
private val info: String = "Hello"
fun printInfo() = println(info)
}
Reason for using Java reflection APIs
It is not possible to do with Kotlin reflection APIs since no setter code is generated for a read-only (val) property. So to change it, we need to use Java reflection APIs which is more low-level. First, we use Tools -> Kotlin -> Show Kotlin Bytecode to see what the generated bytecode looks like. Then we see this:
// ================MainClass.class =================
// class version 50.0 (50)
// access flags 0x31
public final class MainClass {
// access flags 0x12
private final Ljava/lang/String; info = "Hello"
// ...
i.e that the info fields in the MainClass Kotlin class causes the compiler to emit JVM code for a regular MainClass Java class with a final String info field. So to change it, we can use Java reflection APIs, as in the code above.
Kotlin reflection API attempt
If the field would have been private var you would be able to Use Kotlin reflection APIs like this:
f?.let {
val mutableProp = it as KMutableProperty<*>
it.isAccessible = true
mutableProp.setter.call(mainClass, "set from Kotlin reflection")
val w = it.get(mainClass) as String
println(w)
}
but if you try this with private val you will get the below exception
Exception in thread "main" java.lang.ClassCastException: class kotlin.reflect.jvm.internal.KProperty1Impl cannot be cast to class kotlin.reflect.KMutableProperty (kotlin.reflect.jvm.internal.KProperty1Impl and kotlin.reflect.KMutableProperty are in unnamed module of loader 'app')
at MainKt.main(main.kt:107)
at MainKt.main(main.kt)
since no setter code is generated for val fields, and thus the info property will have a Kotlin Reflection API type of KProperty and not KMutableProperty.
This is working solution
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
class MySolution {
var name = ""
var email = ""
}
#Suppress("UNCHECKED_CAST")
fun main() {
//Dummy Result Set
val rs = mapOf<String,String>("name" to "My Name", "email" to "My Email")
val mySol = MySolution();
val xyzMp = MySolution::class.memberProperties;
xyzMp.forEach { mp ->
val prop = mp as KMutableProperty<String>
prop.setter.call(mySol, rs[mp.name])
}
println(mySol.name)
println(mySol.email)
println("*****Enjoy*******")
output
My Name
My Email
**** *Enjoy*******
Recently we upgraded one of our enum class to sealed class with objects as sub-classes so we can make another tier of abstraction to simplify code. However we can no longer get all possible subclasses through Enum.values() function, which is bad because we heavily rely on that functionality. Is there a way to retrieve such information with reflection or any other tool?
PS: Adding them to a array manually is unacceptable. There are currently 45 of them, and there are plans to add more.
This is how our sealed class looks like:
sealed class State
object StateA: State()
object StateB: State()
object StateC: State()
....// 42 more
If there is an values collection, it will be in this shape:
val VALUES = setOf(StateA, StateB, StateC, StateC, StateD, StateE,
StateF, StateG, StateH, StateI, StateJ, StateK, StateL, ......
Naturally no one wants to maintain such a monster.
In Kotlin 1.3+ you can use sealedSubclasses.
In prior versions, if you nest the subclasses in your base class then you can use nestedClasses:
Base::class.nestedClasses
If you nest other classes within your base class then you'll need to add filtering. e.g.:
Base::class.nestedClasses.filter { it.isFinal && it.isSubclassOf(Base::class) }
Note that this gives you the subclasses and not the instances of those subclasses (unlike Enum.values()).
With your particular example, if all of your nested classes in State are your object states then you can use the following to get all of the instances (like Enum.values()):
State::class.nestedClasses.map { it.objectInstance as State }
And if you want to get really fancy you can even extend Enum<E: Enum<E>> and create your own class hierarchy from it to your concrete objects using reflection. e.g.:
sealed class State(name: String, ordinal: Int) : Enum<State>(name, ordinal) {
companion object {
#JvmStatic private val map = State::class.nestedClasses
.filter { klass -> klass.isSubclassOf(State::class) }
.map { klass -> klass.objectInstance }
.filterIsInstance<State>()
.associateBy { value -> value.name }
#JvmStatic fun valueOf(value: String) = requireNotNull(map[value]) {
"No enum constant ${State::class.java.name}.$value"
}
#JvmStatic fun values() = map.values.toTypedArray()
}
abstract class VanillaState(name: String, ordinal: Int) : State(name, ordinal)
abstract class ChocolateState(name: String, ordinal: Int) : State(name, ordinal)
object StateA : VanillaState("StateA", 0)
object StateB : VanillaState("StateB", 1)
object StateC : ChocolateState("StateC", 2)
}
This makes it so that you can call the following just like with any other Enum:
State.valueOf("StateB")
State.values()
enumValueOf<State>("StateC")
enumValues<State>()
UPDATE
Extending Enum directly is no longer supported in Kotlin. See
Disallow to explicitly extend Enum class : KT-7773.
With Kotlin 1.3+ you can use reflection to list all sealed sub-classes without having to use nested classes: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/sealed-subclasses.html
I asked for some feature to achieve the same without reflection: https://discuss.kotlinlang.org/t/list-of-sealed-class-objects/10087
Full example:
sealed class State{
companion object {
fun find(state: State) =
State::class.sealedSubclasses
.map { it.objectInstance as State}
.firstOrNull { it == state }
.let {
when (it) {
null -> UNKNOWN
else -> it
}
}
}
object StateA: State()
object StateB: State()
object StateC: State()
object UNKNOWN: State()
}
A wise choice is using ServiceLoader in kotlin. and then write some providers to get a common class, enum, object or data class instance. for example:
val provides = ServiceLoader.load(YourSealedClassProvider.class).iterator();
val subInstances = providers.flatMap{it.get()};
fun YourSealedClassProvider.get():List<SealedClass>{/*todo*/};
the hierarchy as below:
Provider SealedClass
^ ^
| |
-------------- --------------
| | | |
EnumProvider ObjectProvider ObjectClass EnumClass
| |-------------------^ ^
| <uses> |
|-------------------------------------------|
<uses>
Another option, is more complicated, but it can meet your needs since sealed classes in the same package. let me tell you how to archive in this way:
get the URL of your sealed class, e.g: ClassLoader.getResource("com/xxx/app/YourSealedClass.class")
scan all jar entry/directory files in parent of sealed class URL, e.g: jar://**/com/xxx/app or file://**/com/xxx/app, and then find out all the "com/xxx/app/*.class" files/entries.
load filtered classes by using ClassLoader.loadClass(eachClassName)
check the loaded class whether is a subclass of your sealed class
decide how to get the subclass instance, e.g: Enum.values(), object.INSTANCE.
return all of instances of the founded sealed classes
If you want use it at child class try this.
open class BaseSealedClass(val value: String, val name: Int) {
companion object {
inline fun<reified T:BaseSealedClass> valueOf(value: String): T? {
return T::class.nestedClasses
.filter { clazz -> clazz.isSubclassOf(T::class) }
.map { clazz -> clazz.objectInstance }
.filterIsInstance<T>()
.associateBy { it.value }[value]
}
inline fun<reified T:BaseSealedClass> values():List<T> =
T::class.nestedClasses
.filter { clazz -> clazz.isSubclassOf(T::class) }
.map { clazz -> clazz.objectInstance }
.filterIsInstance<T>()
}
}
#Stable
sealed class Theme(value: String, name: Int): BaseSealedClass(value, name) {
object Auto: Theme(value = "auto", name = R.string.setting_general_theme_auto)
object Light: Theme(value= "light", name = R.string.setting_general_theme_light)
object Dark: Theme(value= "dark", name = R.string.setting_general_theme_dark)
companion object {
fun valueOf(value: String): Theme? = BaseSealedClass.valueOf(value)
fun values():List<Theme> = BaseSealedClass.values()
}
}
For a solution without reflection this is a library that supports generating a list of types to sealed classes at compile time:
https://github.com/livefront/sealed-enum
The example in the docs
sealed class Alpha {
object Beta : Alpha()
object Gamma : Alpha()
#GenSealedEnum
companion object
}
will generate the following object:
object AlphaSealedEnum : SealedEnum<Alpha> {
override val values: List<Alpha> = listOf(
Alpha.Beta,
Alpha.Gamma
)
override fun ordinalOf(obj: Alpha): Int = when (obj) {
Alpha.Beta -> 0
Alpha.Gamma -> 1
}
override fun nameOf(obj: AlphaSealedEnum): String = when (obj) {
Alpha.Beta -> "Alpha_Beta"
Alpha.Gamma -> "Alpha_Gamma"
}
override fun valueOf(name: String): AlphaSealedEnum = when (name) {
"Alpha_Beta" -> Alpha.Beta
"Alpha_Gamma" -> Alpha.Gamma
else -> throw IllegalArgumentException("""No sealed enum constant $name""")
}
}
The short version is
State::class.sealedSubclasses.mapNotNull { it.objectInstance }
I have a framework written in Java that, using reflection, get the fields on an annotation and make some decisions based on them. At some point I am also able to create an ad-hoc instance of the annotation and set the fields myself. This part looks something like this:
public #interface ThirdPartyAnnotation{
String foo();
}
class MyApp{
ThirdPartyAnnotation getInstanceOfAnnotation(final String foo)
{
ThirdPartyAnnotation annotation = new ThirdPartyAnnotation()
{
#Override
public String foo()
{
return foo;
}
};
return annotation;
}
}
Now I am trying to do the exact thing in Kotlin. Bear in mind that the annotation is in a third party jar.
Anyway, here is how I tried it in Kotlin:
class MyApp{
fun getAnnotationInstance(fooString:String):ThirdPartyAnnotation{
return ThirdPartyAnnotation(){
override fun foo=fooString
}
}
But the compiler complains about: Annotation class cannot be instantiated
So the question is: how should I do this in Kotlin?
You can do this with Kotlin reflection:
val annotation = ThirdPartyAnnotation::class.constructors.first().call("fooValue")
In the case of annotation having no-arg constructor (e.g. each annotation field has a default value), you can use following approach:
annotation class SomeAnnotation(
val someField: Boolean = false,
)
val annotation = SomeAnnotation::class.createInstance()
This is the solution I might have found but feels like a hack to me and I would prefer to be able to solve it within the language.
Anyway, for what is worth,it goes like this:
class MyApp {
fun getInstanceOfAnnotation(foo: String): ThirdPartyAnnotation {
val annotationListener = object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
return when (method?.name) {
"foo" -> foo
else -> FindBy::class.java
}
}
}
return Proxy.newProxyInstance(ThirdPartyAnnotation::class.java.classLoader, arrayOf(ThirdPartyAnnotation::class.java), annotationListener) as ThirdPartyAnnotation
}
}