Data class Kotlin to Java Class - java

I have a kotlin data class and I'm trying to call it from Java method.
data class Item (
#PrimaryKey(autoGenerate = true) var var1: Long? ,
#ColumnInfo(name ="var1") var var2: Long){}
From Java , I'm trying to save a list of Item so I need to instance Data class. I don't understand how I can do it.

Instantiating a data class is not different from instatiating a "normal" Kotlin class.
From your Java code, you instantiate it as if it were a Java class:
Item item = new Item(1L, 2L);
Just for reference, a data class is a class that automatically gets the following members (see documentation here):
equals()/hashCode() pair;
toString() of the form "MyClass(field1=value1, field2=value2)";
componentN() functions corresponding to the properties in their order of declaration; this can be useful for destructuring declarations, such as:
data class Item(val val1: Long, val val2: Long)
fun main(args: Array<String>) {
val item = Item(1L, 2L)
val (first, second) = item
println("$first, $second")
}
This will print: 1, 2
copy() function.

Your data class will be like this :
data class Item (#PrimaryKey(autoGenerate = true) var var1: Long?,
#ColumnInfo(name ="var1") var var2: Long);
From Java you can create create object like this:
Item item=new Item(1L,2L);
long firstValue=item.getVar1();
long secondValue=item.getVar2();
If you want to create instance in kotlin it will be like:
val item=Item(1L,2L);
val firstValue:Long?=item.var1;
val secondValue:Long?=item.var2;

Related

Copy most attributes from one class object to another class

Between two separate data classes, Person and PersonRecord, which share the same attribute names, I want an elegant way to copy the values from one class's attributes to the other's.
I have a data class, say for example Person, that defines the business logic data of a person in the application.
import kotlinx.serialization.Serializable
data class Person(
val id: String,
val name: String,
val age: Int,
val currentEmployment: Employment,
val workPermit: WorkPermit
)
#Serializable
data class Employment(
val employer: String,
val job: String,
val yearsWithEmployer: Double
)
#Serializable
data class WorkPermit(
val nationality: String,
val visa: String
)
I need to use these with an AWS DynamoDB client, but this question doesn't really concern DynamoDB specifically. I'll explain my usage below.
For several reasons, I've decided to implement a DAO class that is essentially a copy of the class Person, called PersonRecord except the fields containing complex types, i.e., Employment and WorkPermit, are stored as Strings instead. Also, all the fields are mutable and nullable. I had to make it this way because it's supposed to be a mapper class for DynamoDB Enhanced Client (doc).
Annotating this class as #DynamoDbBean defines how the client writes items into a specified table.
package util
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable
import software.amazon.awssdk.enhanced.dynamodb.Key
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey
#DynamoDbBean
internal data class PersonRecord(
#get: DynamoDbPartitionKey
#get: DynamoDbSortKey
var id: String? = null,
var name: String? = null,
var age: Int? = null,
var currentEmployment: String? = null,
var workPermit: String? = null,
)
class PersonDao(
ddb: DynamoDbEnhancedClient,
personTableName: String
) {
private val personTable: DynamoDbTable<PersonRecord> = ddb.table(
personTableName,
TableSchema.fromBean(PersonRecord::class.java)
)
private fun toPersonRecord(person: Person): PersonRecord =
PersonRecord(
id = person.id,
name = person.name,
age = person.age,
currentEmployment = Json.encodeToString(person.currentEmployment),
workPermit = Json.encodeToString(person.workPermit)
)
private fun toPerson(personRecord: PersonRecord): Person =
Person(
id = personRecord.id!!,
name = personRecord.name!!,
age = personRecord.age!!,
currentEmployment = Json.decodeFromString(
personRecord.currentEmployment!!
),
workPermit = Json.decodeFromString(
personRecord.workPermit!!
)
)
fun writePerson(person: Person) =
personTable.putItem(toPersonRecord(person))
fun readPerson(id: String): Person? {
val personRecord = personTable.getItem(
Key.builder()
.partitionValue(id)
.build()
)
return if (personRecord != null) toPerson(personRecord)
else null
}
}
I am using the public functions readPerson and writePerson to read and write the pretty Person class, while these functions internally convert to and fro PersonRecord.
Is there a way to copy between the different classes Person and PersonRecord more elegantly? If, in the future, we change the shape of Person slightly, there's a lot to change in the PersonRecord and PersonDao classes too. In particular, I need a way to handle decoding String to Employment and WorkPermit, and vice-versa.
In the example above, it'd be trivial to add a field or two, but in my actual application I'm dealing with over a dozen fields, and a bunch of unit tests intricately involved with the fields themselves.
Someone suggested to use class reflections, but I don't understand how I'd use it based on what the Kotlin docs describe.
You can try to read Person properties into a map via reflections (there is no other way) and use delegated properties feature to construct PersonRecord from that map.
https://kotlinlang.org/docs/delegated-properties.html#storing-properties-in-a-map
Here is a sample of reading via reflection https://stackoverflow.com/a/38688203/8642957
Yes, MapStruct is great and it's available in kotlin via kapt.

How to access enclosed element's Variable annotations in annotation processor?

I am creating a string of data class for our API optimization like this
data class ex:
#MyAnnotation
data class Cast(
#Json(name = "cast_id")
val castId: Int,
val name: String,
#Json(name = "profile_path")
val profilePath: String?
)
wanted result:
{"cast_id":1, "name":1, "profile_path":1}
but currently able to generate it
"{"castId":1, "name":1, "profilePath":1}"
I want access #JSON (or #SerializedName) annotation and its value, how do I get it?
then Using Kotlin poet to create class with this string init.

Getting getters (or methods or properties) list in generic class with Kotlin

I can't figure out how should i deal with generics in kotlin.
I'm writing a history class for changes made on generic objects, which should get any type of class as parameter: after that, I would compare the old object values with the new object values, and if I found a difference, I'll write that in my data class.
I've succedeed doing that with java with bean.getClass().getMethods();, but I want to trying move to Kotlin.
class ChangeHistoryUtils<T> (val originalBean : T, username : String , var modifiedBean: T? = null) {
data class ChangeHistory(val username: String, val fieldName : String,
val oldValue : String , val newValue : String , val date : LocalDate = LocalDate.now())
fun compareBeans(){
//how to get all originalBean getters and its values?
}
}
I'm actually stuck here: how should obtain all the getters in my T object?
Let's guess i'll receive a class which with 10 getters, I want to call all these 10 getters in originalBean, and comparing its value with the ones in modifiedBean. If different, I will write it in my ChangeHistory
Thanks
You need to ensure that T itself is not a nullable type, i.e. use something like where T : Any on the class declaration, e.g.:
class ChangeHistoryUtils<T> (originalBean : T, username : String , modifiedBean: T? = null) where T : Any
If you do that you can afterwards just access the methods as you did in Java, e.g. if you just want to reuse the code you already have:
fun compareBeans(){
originalBean::class.java.methods // this is actually your originalBean.getClass().getMethods() !
// just print the methods for now...
.forEach(::println)
}
But as you are using Kotlin you may rather want to use the Kotlin approach then, e.g. just showing the properties, or similar:
originalBean::class.memberProperties
// again just printing them:
.forEach(::println)
You then need to add kotlin-reflect as dependency. You may also want to check the Kotlin reference regarding reflection.

Append to Kotlin data class ArrayList

I have the following data class intended for use in an Android application running Kotlin version 1.2.51:
data class Data(var a: ArrayList<String>, var b: String)
As you can see, a is an ArrayList. I want to append elements from another array into a. I've tried this:
itemsToAppend.forEach {
Data.a.add(it)
}
However, Android Studio determines that a is an unresolved reference. How exactly does one append an item to such an ArrayList?
Thanks.
Data classes are not object classes. You will have to initialise them before you can use it
val d= Data(ArrayList(), "demo")
itemsToAppend.forEach {
d.a.add(it)
}
create an instance of Data:
var a: ArrayList<String> = arrayListOf()
var data = Data(a, "something")
and use data in your loop
If you want to access you list staticly do this:
data class D(var a: ArrayList<String>) { // a can't be used as D.a
companion object {
var ab: ArrayList<String> = ArrayList() // ab can be used as D.ab
}
}

No Java class corresponding to Product with Serializable with Base found

I've written two case class that extends Base abstract class. I have two list of each class (listA and listB). When I want to merge these two list, I can't convert the final list to Apache Spark 1.6.1 Dataset.
abstract class Base
case class A(name: String) extends Base
case class B(age: Int) extends Base
val listA: List[A] = A("foo")::A("bar")::Nil
val listB: List[B] = B(10)::B(20)::Nil
val list: List[Base with Product with Serializable] = listA ++ listB
val result: RDD[Base with Product with Serializable] = sc.parallelize(list).toDS()
Apache Spark will rise this Exception:
A needed class was not found. This could be due to an error in your runpath. Missing class: no Java class corresponding to Base with Product with Serializable found
java.lang.NoClassDefFoundError: no Java class corresponding to Base with Product with Serializable found
at scala.reflect.runtime.JavaMirrors$JavaMirror.typeToJavaClass(JavaMirrors.scala:1299)
at scala.reflect.runtime.JavaMirrors$JavaMirror.runtimeClass(JavaMirrors.scala:192)
at scala.reflect.runtime.JavaMirrors$JavaMirror.runtimeClass(JavaMirrors.scala:54)
at org.apache.spark.sql.catalyst.encoders.ExpressionEncoder$.apply(ExpressionEncoder.scala:50)
at org.apache.spark.sql.SQLImplicits.newProductEncoder(SQLImplicits.scala:41)
When I want to create RDD from list Spark doesn't throw any Exception, But when I convert RDD to Dataset with toDS() method this prior exception will throw.
First, you can get a saner type for list by making it a List[Base] explicitly or by adding Base extends Product with Serializable if the intention is for it only to be extended by case classes/objects. But this isn't enough, because
Spark 1.6 comes with support for automatically generating encoders for a wide variety of types, including primitive types (e.g. String, Integer, Long), Scala case classes, and Java Beans.
Note that abstract classes like Base are not supported. And custom encoders aren't supported either. Though you could try using the kryo (or javaSerialization, as the last resort) encoder, see How to store custom objects in Dataset?.
Here is complete working example:
abstract class Base extends Serializable with Product
case class A(name: String) extends Base
case class B(age: Int) extends Base
object BaseEncoder {
implicit def baseEncoder: org.apache.spark.Encoder[Base] = org.apache.spark.Encoders.kryo[Base]
}
val listA: Seq[A] = Seq(A("a"), A("b"))
val listB: Seq[B] = Seq(B(1), B(2))
val list: Seq[Base] = listA ++ listB
val ds = sc.parallelize(list).toDS

Categories