How to pass arguments to super using KotlinPoet - kotlin

I am using KotlinPoet to generate some code. I have an abstract class with some parameters. Let´s say:
abstract class Foo (val foo: String)
And I want to use Kotlin Poet so I can create an implementation of this class in the following way:
class Bar (val bar: String) : Foo ("I am bar")
My code generation looks this way:
// Create the constructor function
val constructorSpec = FunSpec.constructorBuilder().addParameter("bar", String::class)
// Create the property definition
val propertySpec = PropertySpec.builder("bar",String::class).initializer("bar").build()
// Create the class definition
val classSpec = TypeSpec.classBuilder("Bar").superclass(Foo::class).primaryConstructor(constructorSpec).
.addProperty(propertySpec)
I have try this two ways to get the results, but none of them worked:
// First method using the TypeSpec
classSpec.addSuperclassConstructorParameter(CodeBlock.of("%S","I am bar"))
// Second method using the FunSpec
constructorSpec.callSuperConstructor(CodeBlock.of("%S","I am bar"))
Is there any way I can achieve passing parameters to the super class?

Related

Easiest way to modify value passed to inline class constructor

I'm trying to use inline classes in Kotlin to create a class inlining the String class, such that if I have an instance of my class that it will always be true for the contained string that s == s.trim().
I was initially expecting there to be a straightforward way to do this, like perhaps:
#JvmInline
value class Trimmed private constructor(val str: String) : {
constructor(s : String) : super(s.trim())
}
but that doesn't work, and neither do the other direct approaches I considered ("this(s.trim())", etc.).
This problem has turned out to be surprisingly tricky:
Kotlin seems to provide no easy way to have the primary constructor filter or modify the data that is passed to the constructor of the contained String object.
Even if I make the primary constructor private, I can't declare another constructor with the same signature (taking a single String as a parameter).
If this were a normal (non-inlined) class, I could just set the value after superclass class construction (e.g. "init { str = str.trim() }", but since it's an inline class, I can't do that. ("this=this.trim()" doesn't work either, and String objects themselves are immutable so I can't change the contents of 'str'.)
I tried making the class constructor private and creating a factory function in the same file with the same name as the class, but then I couldn't call the class constructor from within the factory function due to access restrictions.
I then tried making the factory function within the class's companion object, but then Kotlin tried to make that function call itself recursively instead of calling the class's constructor. I wasn't able to find a way to syntactially disambiguate this. I managed to work around this by creating a file-private typealias to give another name for the class so I could call the constructor from within the factory function. (Annoyingly, I couldn't declare the typealias in the companion object next to the factory function: I had to declare it outside.)
This worked, but seemed ugly:
typealias Trimmed2 = Trimmed
#JvmInline
value class Trimmed private constructor(val str: String) {
init { assert(str == str.trim()) }
companion object {
// Kotlin won't let me put the typealias here. :-(
fun Trimmed(s: String): Trimmed = Trimmed2(s.trim()) // Don't want recursion here!
}
}
Another working solution is here, using a private constructor with a dummy argument. Of course Kotlin complained that the dummy argument was unused and so I had to put in a big (why is it so big?) annotation suppressing the warning, which is, again, ugly:
#JvmInline
value class Trimmed private constructor(val str: String) {
private constructor (untrimmed: String, #Suppress("UNUSED_PARAMETER") dummy: Unit) : this(untrimmed.trim())
init { assert(str == str.trim()) }
companion object {
fun Trimmed(s: String): Trimmed = Trimmed(s, Unit)
}
}
Is there a simpler, cleaner way to do this? For instance, a syntactic way to clarify to Kotlin that the companion function is trying to call the class constructor and not itself and so avoid the need for a dummy parameter?
Goals:
Code to construct instances of the class from outside this file should look like constructing an instance of a normal class: 'Trimmed("abc")', not using some factory function with a different name (e.g. "of" or "trimmedOf") or other alternate syntax.
It should be impossible to construct the object containing an untrimmed string. Outside code, and the Trimmed class itself, should be able to trust that if a Trimmed instance exists, that its contained str will be a trimmed string.

Use Kotlins copy function with interface

I currently have a model similar to:
interface FooInterface {
val param: String
}
data class Foo(override val param: String) : FooInterface
data class Bar(override val param: String) : FooInterface
Classes of the type FooInterface are then stored and modified in a list. For example:
val list: List<FooInterface> = listOf(Foo("abc"), Bar("def"))
list.forEach {
it.copy(param = "test")
}
This is not possible. And while I could understand that this makes sense, because the classes Foo and Bar could have more parameters. Casting would probably work, but this doesn't scale well, when using a lot of classes implementing the interface. But I feel like there is a way to achieve this, that I'm overseeing.

Custom Class Implement a Function Type

Found this in kotlin documentation about function and lambda
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
In this page, it says that you can implement function type to class like an interface. How does it work? Can you give me some explanation every each part and give me an example how this is done?
From what I understand, IntTransformer expand/implement anonymous function that takes int as argument and output type, but I still didn't know how does it work...
Thanks
You can think of a function type sort of like an interface that has a single function named invoke with the parameters and return type matching its definition.
So
(Int) -> String
is very much like
interface Foo {
operator fun invoke(param: Int): String
}
And so if a class inherits from (Int) -> String, you would do it in exactly the same way as you would to inherit Foo above. You could say the function inheritance is more versatile, because it allows your class to be passed around as a functional argument directly instead of having to get a reference to its invoke function using ::invoke.

What's the difference between Foo::class.java and Foo::javaClass?

To initialize my logger apparently I need:
val LOGGER : Logger = LoggerFactory.getLogger(Foo::class.java);
If I do:
val LOGGER : Logger = LoggerFactory.getLogger(Foo::javaClass);
It complains that the parameter type is not compatible with getLogger. However according to the API, both are Class<Foo>. How are they different?
The javaClass is an extension property that returns the runtime Java class of an instantiated object. In your case, it is being used as a property reference, which will give you a KProperty1<Foo, Class<Foo>> representing the extension function itself:
val T.javaClass: java.lang.Class<T>
You could use this in combination with a receiver, e.g. if Foo provided a default constructor you could say:
Foo::javaClass.get(Foo())
which may be simplified to:
Foo().javaClass
Using ::class.java on the other hand, gives you the Java Class<?> as described in "class references" directly. All three possibilities in a simple example:
val kProperty1: KProperty1<Foo, Class<Foo>> = Foo::javaClass
kProperty1.get(Foo()) //class de.swirtz.kotlin.misc.Foo
Foo::class.java //class de.swirtz.kotlin.misc.Foo
Foo().javaClass //class de.swirtz.kotlin.misc.Foo
javaClass is an extension property which returns the runtime Java class of an object.
/**
* Returns the runtime Java class of this object.
*/
public inline val <T: Any> T.javaClass : Class<T>
#Suppress("UsePropertyAccessSyntax")
get() = (this as java.lang.Object).getClass() as Class<T>
It can be called on an instance of a class, for example:
println(Foo().javaClass) //class Foo
However, Foo::javaClass give you a property reference of type KProperty1<Foo, Class<Foo>> instead of a Java class instance which can be used to get the class of an instance of Foo through reflection:
val p: KProperty1<Foo, Class<Foo>> = Foo::javaClass
println(p.get(Foo())) //p.get(Foo()) returns a Java class Foo
Therefore, it is wrong to pass a KProperty to LoggerFactory.getLogger() which accepts a Java class.
Foo::javaClass is a reference to a val defined as
inline val <T : Any> T.javaClass: Class<T>
So you'd have to call it on an instance of Foo like foo.javaClass.
Foo::class gives you the actual KClass of Foo and java is a property of KClass defined as
val <T> KClass<T>.java: Class<T>

is it possible to add a template to the getter/setter of a data class?

for example , I want to change all setters this way:
this.a = StringUtils.trim(a);
If it's a java bean, I can do this by modifying the code generating template of the ide. But Intellij seems not support to atomically add getter/setter for kotlin data class.
Is there a way to do this?
There is not a way to do this as of Kotlin 1.1.
A Kotlin data class, for the most part, is a class "to do nothing but hold data".
I think the closest you can get is to validate your data upon class initialization and make your data class properties read-only values. e.g.:
data class Data(val a: String) {
init {
require(a == a.trim())
}
}
The following won't throw an exception:
val a = Data("ab")
val b = a.copy(a = "abc")
While the following will:
val c = a.copy(a = "abc ")
It looks like if you declare the property as private, you can create your own getter/setters for accessing it. This example works for me.
fun main(args: Array<String>) {
var t = test("foo")
t.setHello("bar")
println(t)
}
data class test(private var hello: String) {
fun setHello(blah: String) {
this.hello = blah
}
}
But you will still have an issue when the property is passed in to the constructor. You will probably need to rethink how you are doing this, either declaring the field private and trimming it in the getter, or not using a data class for this instance.