Suppose we have two classes A and B. The framework will require that A will have 0 or more properties that are of type B. The user can have a reference to an instance of B. For simplicity, assume that type B can only be declared within class A.
class A {
private val myFirstB = B(this)
private val mySecondB = B(this)
val listOfB: List<B> = mutableListOf(myFirstB, mySecondB)
}
class B(a: A) {
val myA : A = a
fun doSomething(){
// great stuff
}
}
fun testIt(){
val a = A()
val b1 = a.listOfB[0]
}
Through Kotlin reflection how can we determine the name of the property that is holding the reference to b1 within A when we only have the reference b1. We also know that B has a reference to A. Through the instance B, we can get the instance of A. Using reflection, we can get the properties of A via the declaredMemberProperties property. This can be iterated through to get the names of all the properties. However, I do not understand how to ensure that the name that is retrieved is associated with the reference b1.
As said in comments, your case looks very specific and usually reflection isn't ideal for such cases. The algorithm either has to target this very specific case, including the fact the b1 is always inside a list. Or it would have to support many different cases and "guess" where to look for b1.
Basically, the idea is to get the value of each member and compare it to the searched value:
fun main() {
val a = A()
val b1 = a.listOfB[0]
// prints: "listOfB"
println(findPropertyNameByValue(a, b1))
}
fun findPropertyNameByValue(owner: Any, value: Any): String {
return owner::class.memberProperties.first { prop ->
#Suppress("UNCHECKED_CAST")
val list = (prop as KProperty1<Any, *>).get(owner)
list is List<*> && list.any { it === value }
}.name
}
We can even get "listOfB" from the b1 alone, then the algorithm would have to iterate through members twice, again by guessing if the value is our A or not. But it is technically possible.
After some experimentation, the getter call holds the reference that is needed. Here is some possible code:
import kotlin.reflect.KClass
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.KProperty1
fun main(){
val a = A()
val b1 = a.listOfB[0]
println(b1)
val clz: KClass<out A> = a::class
val properties: Collection<KProperty1<out A, *>> = clz.declaredMemberProperties
for(p in properties){
println(p)
println(p.name)
println(p.getter.call(a))
if (p.getter.call(a)?.equals(b1) == true){
println("This is b1")
}
}
}
class A {
val myFirstB = B(this)
val mySecondB = B(this)
val listOfB: List<B> = mutableListOf(myFirstB, mySecondB)
}
var cnt = 1
class B(a: A) {
val myA : A = a
val s = cnt++
fun doSomething(){
// great stuff
}
}
This has the following output:
ksl.B#4590c9c3
val ksl.A.listOfB: kotlin.collections.List<ksl.B>
listOfB
[ksl.B#4590c9c3, ksl.B#d62fe5b]
val ksl.A.myFirstB: ksl.B
myFirstB
ksl.B#4590c9c3
This is b1
val ksl.A.mySecondB: ksl.B
mySecondB
ksl.B#d62fe5b
As can be seen, the test of if the getter reports that it correctly finds b1
Related
I have a situation where I need to create a copy of data class object. I don't know in advance which of the many data classes I have will come in into the function. I do know, however, that only data classes will be used as input to this function.
This is what didn't work:
fun doSomething(obj: Any): Any {
obj.copy(...) // <- there's no 'copy' on Any
...
}
This is what I really like to do:
fun doSomething(obj: KAnyDataClass): KAnyDataClass {
obj.copy(...) // <- works, data classes have a 'copy' method
...
}
I'm not a Kotlin developer, but it looks like the language does not support dynamic dispatch or traits. You might find success with the dynamic type, which just turns off the type-checker so it won't yell at you for using a method that it doesn't know about. However this opens up the possibility of a runtime error if you pass an argument that actually doesn't have that method.
There is no class or interface for data classes, but we know from the documentation of data classes that there are derived functions componentN and copy in each data class.
We can use that knowledge to write an abstract copy method that calls the copy method of a given arbitrary data class using reflection:
fun <T : Any> copy(data: T, vararg override: Pair<Int, Any?>): T {
val kClass = data::class
if (!kClass.isData) error("expected a data class")
val copyFun = kClass.functions.first { it.name == "copy" }
checkParameters(override, kClass)
val vals = determineComponentValues(copyFun, kClass, override, data)
#Suppress("UNCHECKED_CAST")
return copyFun.call(data, *vals) as T
}
/** check if override of parameter has the right type and nullability */
private fun <T : Any> checkParameters(
override: Array<out Pair<Int, Any?>>,
kClass: KClass<out T>
) {
override.forEach { (index, value) ->
val expectedType = kClass.functions.first { it.name == "component${index + 1}" }.returnType
if (value == null) {
if (!kClass.functions.first { it.name == "component${index + 1}" }.returnType.isMarkedNullable) {
error("value for parameter $index is null but parameter is not nullable")
}
} else {
if (!expectedType.jvmErasure.isSuperclassOf(value::class))
error("wrong type for parameter $index: expected $expectedType but was ${value::class}")
}
}
}
/** determine for each componentN the value from override or data element */
private fun <T : Any> determineComponentValues(
copyFun: KFunction<*>,
kClass: KClass<out T>,
override: Array<out Pair<Int, Any?>>,
data: T
): Array<Any?> {
val vals = (1 until copyFun.parameters.size)
.map { "component$it" }
.map { name -> kClass.functions.first { it.name == name } }
.mapIndexed { index, component ->
override.find { it.first == index }.let { if (it !== null) it.second else component.call(data) }
}
.toTypedArray()
return vals
}
Since this copy function is generic and not for a specific data class, it is not possible to specify overloads in the usual way, but I tried to support it in another way.
Let's say we have a data class and element
data class Example(
val a: Int,
val b: String,
)
val example: Any = Example(1, "x")
We can create a copy of example with copy(example) that has the same elements as the original.
If we want to override the first element, we cannot write copy(example, a = 2), but we can write copy(example, 0 to 2), saying that we want to override the first component with value 2.
Analogously we can write copy(example, 0 to 3, 1 to "y") to specify that we want to change the first and the second component.
I am not sure if this works for all cases since I just wrote it, but it should be a good start to work with.
I've created the following snippet, not anything that I would actually write in production, but just to help me understand how properties are initialized:
class C {
val a: String = run {
println("init a")
f()
}
val b: String = run {
println("init b: a=$a")
a
}
private fun f(): String {
println("f(): a=$a, b=$b")
return b
}
}
fun main() {
println(C().a)
println(C().b)
}
Note that all properties are val and String, so immutable and non-nullable. Yet the output shows that they are null.
Is the result of this code well-defined? Is there somewhere in the language spec that says that a non-nullable property can in fact be null when you access it from the wrong place?
According to the Kotlin/Core spec, the order of the execution is specified,
When a classifier type is initialized using a particular secondary constructor ctor delegated to primary constructor pctor which, in turn, is delegated to the corresponding superclass constructor sctor , the following happens, in this initialization order:
[...]
Each property initialization code as well as the initialization blocks in the class body are invoked in the order of appearance in the
class body;
[...]
but the values of the uninitialised properties are not.
If any of the properties are accessed before they are initialized w.r.t initialization order (e.g., if a method called in an initialization block accesses a property declared after the initialization block), the value of the property is unspecified. It stays unspecified even after the “proper” initialization is performed.
In other words, you will see the messages printed in the order of
init a
f(): a=null, b=null
init b: a=null
null
but the values are not guaranteed to be null.
There might be stronger guarantees in Kotlin/JVM, but there is no spec for that right now.
This is explicitly not an answer, just two small remarks/questions:
First, doesn't one of the three members a, b, or f() need to be set to a or return a value? The way it is in the OPs code, everything is null by definition as there is no non-null value anywhere. The 'call chain' is a –> f() –> b, so if we set b to a value like "start", it would look like this:
class C {
val a: String = run { f() }
val b: String = run { "start" }
private fun f(): String = b
}
fun main() {
val c = C()
println(c.a) // Output: null
println(c.b) // Output: "start"
}
Secondly, if the order is changed so that b is appearing in the code before the other two members we get a different result:
class C {
val b: String = run { "start" }
val a: String = run { f() }
private fun f(): String = b
}
fun main() {
val c = C()
println(c.a) // Output: "start"
println(c.b) // Output: "start"
}
Suppose I have two methods:
private fun method1(a: A): A {
return a.copy(v1 = null)
}
private fun method2(a: A): A {
return a.copy(v2 = null)
}
Can I write something like:
private fun commonMethod(a: A, variableToChange: String): A {
return a.copy($variableToChange = null)
}
Another words, can I use a variable to refer to a named argument?
If I understand correctly what you are trying to archive I would recommend to pass a setter to the method e.g.
fun <A> changer (a: A, setter: (a: A) -> Unit ) {
// do stuff
setter(a)
}
Is this what you are looking for?
A possible solution for this problem (with usage of reflection) is:
inline fun <reified T : Any> copyValues(a: T, values: Map<String, Any?>): T {
val function = a::class.functions.first { it.name == "copy" }
val parameters = function.parameters
return function.callBy(
values.map { (parameterName, value) ->
parameters.first { it.name == parameterName } to value
}.toMap() + (parameters.first() to a)
) as T
}
This works with all data classes and all classes that have a custom copy function with the same semantics (as long as the parameter names are not erased while compiling). In the first step the function reference of the copy method is searched (KFunction<*>). This object has two importent properties. The parameters property and the callBy function.
With the callBy function you can execute all function references with a map for the parameters. This map must contain a reference to the receiver object.
The parameters propery contains a collection of KProperty. They are needed as keys for the callBy map. The name can be used to find the right KProperty. If a function as a parameter that is not given in the map it uses the default value if available or throws an exception.
Be aware that this solution requires the full reflection library and therefore only works with Kotlin-JVM. It also ignores typechecking for the parameters and can easily lead to runtime exceptions.
You can use it like:
data class Person (
val name: String,
val age: Int,
val foo: Boolean
)
fun main() {
var p = Person("Bob", 18, false)
println(p)
p = copyValues(p, mapOf(
"name" to "Max",
"age" to 35,
"foo" to true
))
println(p)
}
// Person(name=Name, age=15, foo=false)
// Person(name=Max, age=35, foo=true)
I am designing a DSL and run into a requirement where I have a variable which could be assigned to different ways. Greatly simplified, I would like to set value property either by an integer or by an expression in String. (The real need is even more complex.)
I would like to write in my DSL:
value = 42
or
value = "6*7"
Behind the scene, the value will be stored in a DynamicValue<Int> structure which contains either an integer or the expression.
class DynamicValue<T>(dv : T?, expr : String) {
val directValue : T? = dv
val script : String? = expr
...
}
I tried several ways (delegate, class, etc), but none of them provided these syntax.
Is there a way to declare this union like structure?
What do you think about the following syntax:
value(42)
value("6*7")
//or
value+=42
value+="6*7"
You can do this with operator functions:
class DynamicValue<T>() {
var dv: T? = null
var expr: String? = null
operator fun invoke(dv : T) {
this.dv = dv
this.expr = null
}
operator fun invoke(expr: String) {
this.dv = null
this.expr = expr
}
operator fun plusAssign(dv : T) {
this.dv = dv
this.expr = null
}
operator fun plusAssign(expr: String) {
this.dv = null
this.expr = expr
}
}
You can't redefine the assign operator in Kotlin, therefor the pure syntax value=42 is not possible.
But I wouldn't go with operator functions, it's to magical. I would do this:
val value = DynamicValue<Int>()
value.simple=42
value.expr="6*7"
class DynamicValue2<T>() {
private var _dv: T? = null
private var _expr: String? = null
var simple: T?
get() = _dv
set(value) {
_dv = value
_expr = null
}
var expr: String?
get() = _expr
set(value) {
_expr = value
_dv = null
}
}
Rene's answer gave me the lead and finally I turned up with this solution.
In this solution I took all my requirements in (the ones I dropped out in my original question) so this became much more complicated than my original question would have required.
My whole requirement was to be able to add static values or scripts (snippets) running on a well guarded context. These script would be stored, and executed later. I wanted to enable the whole power of the IDE when writing the script, but would like to guard my scripts from code injections and help the user to use only the context values the script requires.
The trick I used to achieve this is to enable adding script in kotlin, but before I run the whole DSL script and create the business objects, I convert the script into a string. (This string will be executed later in a guarded, wrapped context by JSR233 engine.) This conversation forced me to tokenize the whole script before execution and search/replace some of the tokens. (The whole tokenizer and converter is rather long and boring, so I won't insert here.)
First approach
What my goal was to be able to write any of this:
myobject {
value = static { 42 } // A static solution
value = static { 6 * 7 } // Even this is possible
value = dynamic{ calc(x, y) } // A pure cotlin solution with IDE support
value = dynamic("""calc(x * x)""") // This is the form I convert the above script to
}
where calc, x and y are defined in the context class:
class SpecialScriptContext : ScriptContextBase() {
val hello = "Hello"
val x = 29
val y = 13
fun calc(x: Int, y: Int) = x + y
fun greet(name: String) = println("$hello $name!")
}
So let's see the solution! First I need a DynamicValue class to hold one of the values:
class DynamicValue<T, C : ScriptContextBase, D: ScriptContextDescriptor<C>>
private constructor(val directValue: T?, val script: String?) {
constructor(value: T?) : this(value, null)
constructor(script: String) : this(null, script)
}
This structure will ensure that exactly one of the options (static, script) will be set. (Don't bother with the C and D type parameters, they are for context-based script support.)
Then I made top level DSL functions to support syntax:
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> static(block: () -> T): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(value = block.invoke())
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(s: String): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(script = s)
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(block: C.() -> T): DynamicValue<T, C, D> {
throw IllegalStateException("Can't use this format")
}
An explanation to the third form. As I wrote before, I don't want to execute the block of the function. When the script is executed, this form is converted to the string form, so normally this function would never appear in the script when executed. The exception is a sanity warning, which would never be thrown.
Finally added the field to my business object builder:
#PlsDsl
class MyObjectBuilder {
var value: DynamicValue<Int, SpecialScriptContext, SpecialScriptContextDescriptor>? = null
}
Second approach
The previous solution worked but had some flaws: the expression was not associated with the variable it set, neither with the entity the value was set in. With my second approach I solved this problem and removed the need of equal sign and most of the unnecessary curly brackets.
What helped: extension functions, infix functions and sealed classes.
First, I split the two value types into separated classes defined a common ancestor:
sealed class Value<T, C : ScriptContextBase> {
abstract val scriptExecutor: ScriptExecutor
abstract val descriptor: ScriptContextDescriptor<C>
abstract val code: String
abstract fun get(context: C): T?
}
class StaticValue<T, C : ScriptContextBase>(override val code: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>,
val value: T? = null
) : Value<T, C>() {
override fun get(context: C) = value
constructor(oldValue: Value<T, C>, value: T?) : this(oldValue.code, oldValue.scriptExecutor, oldValue.descriptor, value)
}
class DynamicValue<T, C : ScriptContextBase>(override val code: String,
script: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>)
: Value<T, C>() {
constructor(oldValue: Value<T, C>, script: String) : this(oldValue.code, script, oldValue.scriptExecutor, oldValue.descriptor)
private val scriptCache = scriptExecutor.register(descriptor)
val source = script?.replace("\\\"\\\"\\\"", "\"\"\"")
private val compiledScript = scriptCache.register(generateUniqueId(code), source)
override fun get(context: C): T? = compiledScript.execute<T?>(context)
}
Note, that I made the primary constructor internal and created a kind of copy and alter constructor. Then I defined the new functions as extension of the common ancestor and marked them infix:
infix fun <T, C : ScriptContextBase> Value<T, C>.static(value: T?): Value<T, C> = StaticValue(this, value)
infix fun <T, C : ScriptContextBase> Value<T, C>.expr(script: String): Value<T, C> = DynamicValue(this, script)
infix fun <T, C : ScriptContextBase> Value<T, C>.dynamic(block: C.() -> T): Value<T, C> {
throw IllegalStateException("Can't use this format")
}
Using the secondary copy-and-alter constructor allows to inherit the context sensitive values. Finally I initialize the value inside the DSL builder:
#PlsDsl
class MyDslBuilder {
var value: Value<Int, SpecialScriptContext> = StaticValue("pl.value", scriptExecutor, SpecialScriptContextDescriptor)
var value2: Value<Int, SpecialScriptContext> = StaticValue("pl.value2", scriptExecutor, SpecialScriptContextDescriptor)
}
Everything is in place and now I can use it in my script:
myobject {
value static 42
value2 expr "6 * 7"
value2 dynamic { calc(x, y) }
}
Let's say I'd want to instantiate an object of class A by copying values from class B which is a common practice when, for example, mapping DTO's. To accomplish this in Java or Groovy I'd create a static method on the appropriate DTO with the signature of fromB(A a) and then copy values either with a.val = b.val... in Java or using a.with { val = b.val... } in Groovy.
In Kotlin I've noticed that instance.apply{} is very similar to Groovy's with in that it allows me to directly access the object variables without constantly refering to the object itself since the reference seems to be implied within the closure.
However I've ran into a weird and unexpected error when using apply within companion objects. If I use A().apply {} inside a function of A's companion object I get an error Expression is inaccessible from a nested class 'Companion', use 'inner' keyword to make the class inner Which is weird since I'm calling apply directly on an instance of an object and would thus expect that I should always be able to access it's public properties. Not to mention that it seems like companion objects cannot be set to be inner thus the suggestion in the error message isn't all too helpful.
Here's the full example code:
fun main(args: Array<String>) {
val b = B("Hello", "World")
val a = A.fromB(b)
print("$a.value1 $a.value2")
}
class A() {
var value1: String? = null
var value2: String? = null
companion object {
//This fails with "Expression is inaccessible from a nested class 'Companion', use 'inner' keyword to make the class inner"
fun fromB(b: B): A {
return A().apply {
value1 = b.value3
value2 = b.value4
}
}
}
}
class B(val value3: String, val value4: String) {}
//This works
fun bToA(b: B): A {
return A().apply {
value1 = b.value3
value2 = b.value4
}
}
What is going on here? What am I doing wrong?
This looks like a bug to me. Probably something to do with inline functions (e.g. apply) and companion objects. I suggest searching the JetBrains Bug & Issue Tracker and if you don't find something similar to this create a new issue.
In the meantime I see some alternatives:
Use this (not ideal):
fun fromB(b: B): A {
return A().apply {
this.value1 = b.value3
this.value2 = b.value4
}
}
Move value1 and value2 to A's primary constructor and change fromB(B) to use named arguments (this will still let you define defaults, skip properties when copying, etc.):
class A(var value1: String? = null, var value2: String? = null) {
companion object {
fun fromB(b: B): A {
return A(
value1 = b.value3,
value2 = b.value4
)
}
}
}
UPDATE: In addition to the above you can use b with with:
fun fromB(b: B) = with(b) {
A(
value1 = value3,
value2 = value4
)
}
#MrPlow
I think this is more straightforward way to do, what you want:
fun B.toA(): A {
val self = this;
return A().apply {
value1 = self.value3
value2 = self.value4
}
}
Compare with your example:
val b = B("Hello", "World")
val a = A.fromB(b)
// vs
val a = b.toA();