I am trying to make a simple serializable class. But something goes wrong. Compilation fails for native build. But jvm target works fine.
Superclass:
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlin.native.concurrent.ThreadLocal
#Serializable
sealed class DefaultRequest {
private val id = Companion.id
private val name = this::class::simpleName.get()
#Transient
private val format = Json { encodeDefaults = true }
#ThreadLocal
protected companion object {
private var id = -1
get() { return ++field }
}
override fun toString() = format.encodeToString(this)
}
Subclass:
import kotlinx.serialization.Serializable
#Serializable
class PingRequest : DefaultRequest()
I had a look on this tutorial: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#sealed-classes
It looks very easy and simple. But i don't understand where i did a mistake.
Error log from ide:
Compilation failed: Backend Internal error: Exception during IR lowering
File being compiled: /src/commonMain/kotlin/connection/serializable/requests/PingRequest.kt
The root cause org.jetbrains.kotlin.backend.common.CompilationException was thrown at: org.jetbrains.kotlin.ir.symbols.impl.IrBindableSymbolBase.getOwner(IrPrivateSymbolBase.kt:59)
* Source files:
* Compiler version info: Konan: 1.7.20-Beta / Kotlin: 1.7.20
* Output kind: PROGRAM
UPDATE:
So. After changing the code from this:
private val id = Companion.id
private val name = this::class::simpleName.get()
to this:
private val id: Int
private val name: String
init {
id = Companion.id
name = this::class::simpleName.get()!!
}
the problem has gone. But i still have a question. Why is it not working?
private val id = Companion.id
private val name = this::class::simpleName.get()
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 got an error. Like this :
Error 1 : Platform declaration clash: The following declarations have the same JVM signature (getData()Ljava/lang/Object;):
fun (): I defined in typeErasure2
fun getData(): I defined in typeErasure2
Error 2 : Platform declaration clash: The following declarations have the same JVM signature (getData()Ljava/lang/Object;):
fun (): I defined in typeErasure2
fun getData(): I defined in typeErasure2
fun main(args : Array<String>){
var te = typeErasure("Jennie")
println(te.getData())
var te2 = typeErasure2("Sam")
println(te2.getData())
}
class typeErasure<I>(name : I){
private val data : I = name
fun getData() : I = data
}
class typeErasure2<I>(name : I){
val data : I = name // error 1
fun getData() : I = data // error 2
}
when I use the private keyword the program can run, otherwise it will get an error. anyone can explain to me? :)
This has nothing to do with generics. The problem with your code is that
public fun getData(): I
Is an accesor for "data". But when "data" is a public field, then the accesor is redundant. So when you do:
val someValue = myObject.data
Then the compiler cannot tell if it should use the accessor getData() or it should just point to the field directly.
When getData is private, then the compiler clearly knows that it can't use it so then it will point to the field directly.
class typeErasure2<I>(name : I){
val data : I = name
fun getData() : I = data
}
fun main() {
val obj = typeErasure2<Int>(123)
println(obj.data) // <--- Ask yourself, what does this line do exactly?
}
class typeErasure2<I>(name : I){
val data : I = name
private fun getData() : I = data
}
fun main() {
val obj = typeErasure2<Int>(123)
println(obj.data) // <--- Here it is clear
// it is not the "getData" because that one is private,
// so it must be the public field "data" you are pointing to
}
Kotlin's logic of properties differ slightly from Java's fields.
Whenever you declare any variable in class, its getter and setters are automatically generated, and they can be customized with get() or set {} after it.
Declaring getVariable() manually will result in platform clash, as getter is already defined for the field in the variable declaration and you are creating function with the same name as well.
You can use #JvmField annotation to instruct the compiler to not generate any getter or setter for the field.
#JvmField
val data: I = name
fun getData(): I = data
I'm trying to pass data class to the service-proxy of Vert.x like this:
data class Entity(val field: String)
#ProxyGen
#VertxGen
public interface DatabaseService {
DatabaseService createEntity(Entity entity, Handler<AsyncResult<Void>> resultHandler);
}
However, the service-proxy requires a DataObject as the parameter type.
Below are what I've tried so far.
First, I rewrite the data class as:
#DataObject
data class Entity(val field: String) {
constructor(json: JsonObject) : this(
json.getString("field")
)
fun toJson(): JsonObject = JsonObject.mapFrom(this)
}
Although this works, the code is redundant, so I tried the kapt with the following generator:
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
roundEnv.getElementsAnnotatedWith(ProxyDataObject::class.java).forEach { el ->
val className = el.simpleName.toString()
val pack = processingEnv.elementUtils.getPackageOf(el).toString()
val filename = "Proxy$className"
val classBuilder = TypeSpec.classBuilder(filename)
val primaryConstructorBuilder = FunSpec.constructorBuilder()
val secondaryConstructorBuilder = FunSpec.constructorBuilder().addParameter("json", JsonObject::class)
val secondaryConstructorCodeBlocks = mutableListOf<CodeBlock>()
el.enclosedElements.forEach {
if (it.kind == ElementKind.FIELD) {
val name = it.simpleName.toString()
val kClass = getClass(it) // get the corresponding Kotlin class
val jsonTypeName = getJsonTypeName(it) // get the corresponding type name in methods of JsonObject
classBuilder.addProperty(PropertySpec.builder(name, kClass).initializer(name).build())
primaryConstructorBuilder.addParameter(name, kClass)
secondaryConstructorCodeBlocks.add(CodeBlock.of("json.get$jsonTypeName(\"$name\")"))
}
}
secondaryConstructorBuilder.callThisConstructor(secondaryConstructorCodeBlocks)
classBuilder
.addAnnotation(DataObject::class)
.addModifiers(KModifier.DATA)
.primaryConstructor(primaryConstructorBuilder.build())
.addFunction(secondaryConstructorBuilder.build())
.addFunction(
FunSpec.builder("toJson").returns(JsonObject::class).addStatement("return JsonObject.mapFrom(this)").build()
)
val generatedFile = FileSpec.builder(pack, filename).addType(classBuilder.build()).build()
generatedFile.writeTo(processingEnv.filer)
}
return true
}
Then I can get the correct generated file by simply writing the original data class, but when I execute the building after cleaning, I still get the following error:
Could not generate model for DatabaseService#createEntity(ProxyEntity,io.vertx.core.Handler<io.vertx.core.AsyncResult<java.lang.Void>>): type ProxyEntity is not legal for use for a parameter in proxy
It seems that the generated annotation #DataObject is not processed.
So what should I do? Is there a better solution?
I'm trying to convert the following code to Kotlin AND still have one of the classes (Foo) used by Java. What is the proper way of making this conversion?
Original Java:
public class Foo {
public static final String C_ID = "ID";
public static final String C_NAME = "NAME";
public static final String[] VALUES = {"X", "Y", "Z"};
public static String[] getAll() {
return new String[] {C_ID, C_NAME};
}
}
public class Bar {
public void doStuff() {
String var1 = Foo.C_ID;
String[] array1 = Foo.VALUES;
String[] array2 = Foo.getAll();
}
}
Auto conversion fo Foo to Kotlin
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
Problem:
Bar class can no longer access C_ID or VALUES (error: "private access")
if I put "const" in front of C_ID, it works... but I cannot do the same with VALUES ("const" can ONLY be used on primatives or String)
Is there a different way I should be doing this (so both Java code and Kotlin code can access everything in Foo)?
The current semantics come from Kotlin Beta Candidate:
#JvmField and objects
We have made the strategy for generating pure fields (as opposed to get/set pairs) more predictable: from now on only properties annotated as #JvmField, lateinit or const are exposed as fields to Java clients. Older versions used heuristics and created static fields in objects unconditionally, which is against our initial design goal of having binary-compatibility-friendly APIs by default.
Also, singleton instances are now accessible by the name INSTANCE (instead of INSTANCE$).
According to this and to the reference, there are three ways of working with properties of a Kotlin object from Java:
Use Foo.INSTANCE.
By default, properties of object won't be static fields for Java, but Java can access the properties through Foo object instance -- Foo.INSTANCE.
So the expression will be Foo.INSTANCE.getC_ID().
Mark a property with #JvmStatic annotation:
object Foo {
#JvmStatic val C_ID = "ID"
//...
}
This will generate static getter for C_ID instead of Foo instance getter which will be accessible as Foo.getC_ID().
Use #JvmField annotation on property declaration:
object Foo {
#JvmField val C_ID = "ID"
//...
}
This will make Kotlin compiler generate a static field for Java instead of property.
Then in Java you can access it as a static field: Foo.C_ID.
But it won't work on properties without backing fields like all in your example.
For primitives, as you stated, one can use const which will have the same effect as #JvmField in terms of visibility in Java.
By the way, when it comes to methods, the situation is the same, and there is #JvmStatic annotation for them.
In your foo class you can put those properties and the method inside a companion object:
class Foo {
companion object {
val C_ID:String = "ID"
val C_NAME:String = "NAME"
#JvmField val VALUES = arrayOf("X", "Y", "Z")
fun getAll():Array<String> {
return arrayOf(C_ID, C_NAME)
}
}
}
Then you can call Foo.getAll(), and Foo.C_ID, Foo.C_NAME and Foo.VALUES.
You should be able to access the values "the kotlin way":
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
fun main(args: Array<String>) {
Foo.all.forEach { it->println(it) }
}
With as result:
ID
NAME
Process finished with exit code 0
it's better if you create new kotlin file just for constants.
create Constants.kt file and paste below code.
object Constants {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
in your main activity you can access the constants by the constant name the android studio will automatically import the constants. here is my mainActivity:
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.architecturecompintro.Constants.C_ID
import com.example.architecturecompintro.Constants.C_NAME
import com.example.architecturecompintro.Constants.VALUES
import com.example.architecturecompintro.Constants.all
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val TAG = "info"
Log.i(TAG, C_ID)
Log.i(TAG,C_NAME)
for(item in VALUES) {
Log.i(TAG,item)
}
val arrayItem = all
for(item in arrayItem) {
Log.i(TAG,item)
}
}
}
I was able to get log output successfully