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

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.

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 create a Java instance of the object from Kotlin data class but don't include all fields?

I have the following Kotlin data class:
data class Filter #JvmOverloads constructor(
val key: String? = null,
val operation: String? = null,
val value: String? = null,
val group: String? = null,
val searchInTranslations: Boolean? = null)
I create an instance of this class in Java code. But I want to create an instance with all the fields except for the group field. How can I do it in Java?
I can't assign a default value to group because in some Java classes I do assign a value to this field, in other Java classes I need to omit this field from object creation.
Java doesn't support this, but you can make a simple trick.
Generate two constructors, one with the optional field group, one without it.
So from the classes you don't need to assign a value to that field, simply make a call to the second constructor:
public YourClass(String key, String operation, String value, bool search)
{
this (key, operation, value, null, search); //null for the groups
}

How to get KClass from Kproperty? [duplicate]

Let's say i have any class, like this one:
class SomeClass(val aThing: String, val otherThing: Double)
Then I use reflection to analyze the fields of this class:
for(field in SomeClass.declaredMemberProperties){
}
How can I check the type of each field?
Since Kotlin does not have fields but only properties with backing fields, you should check the return type of the property.
Try this:
class SomeClass(val aThing: String, val otherThing: Double)
for(property in SomeClass::class.declaredMemberProperties) {
println("${property.name} ${property.returnType}")
}
UPDATE:
If the class does not use custom getters and/or setters without backing fields, you can get the type of the backing field like this:
property.javaField?.type
As a complete example, here is your class with an additional val property called foo with a custom getter (so no backing field is created). You will see that getJavaField() of that property will return null.
class SomeClass(val aThing: String, val otherThing: Double) {
val foo : String
get() = "foo"
}
for(property in SomeClass::class.declaredMemberProperties) {
println("${property.name} ${property.returnType} ${property.javaField?.type}")
}
UPDATE2:
Using String::class.createType() will return the KType for every KClass, so you can use e.g. property.returnType == String::class.createType() to find out if it's a (kotlin) String.

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.

Data class Kotlin to Java Class

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;

Categories