Use Kotlin's data class in service-proxy of Vert.x - kotlin

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?

Related

Kotlin reflection on Anonymous object using kotlin-reflect

I am receiving a json object in which there is a property I don't know the name of at compile time.
The name of the property is stored in a variable.
Since the name of the property may vary the JSON is parsed as an Anonymous object.
Is it possible to read the value of the property using reflection using the name stored in the variable ?
I tried with code resembling this:
jsonResponse::class.memberProperties.find { it.name == variableName }
with no success.
val decodedToken = JWT(jwtString)
decodedToken.getClaim("useful_claim").asObject(Any::class.java)?.let {
// Get the property that matches the variable name
val reflectProp = res::class.memberProperties.find { it.name == BuildConfig.VARIABLE_NAME }
// Check that the property was found and exists
if (reflectProp is KMutableProperty<*>) {
(reflectProp.getter.call(res, BuildConfig.VARIABLE_NAME) as? List<*>)?.let {
// Return it as a list of existing MyClass
return it.filterIsInstance<MyClass>()
}
}
}
After the comments made by #Joffrey and #broot I tried without the JWT library.
Here is the code:
// Parcelable classes
#Parcelize
#JsonClass(generateAdapter = true)
data class JwtCustomResponse(
// This maps the variable name from the buildconfig on a known field name that can be used later
#field:Json(name = BuildConfig.VARIABLE_NAME) val appResourceAccess: MyCustomClass? = MyCustomClass()
) : Parcelable
#Parcelize
#JsonClass(generateAdapter = true)
data class JWTBody(
#field:Json(name = "resource_access") val resourceAccess: JwtCustomResponse? = JwtCustomResponse(),
) : Parcelable
// Custom JWT deserializer
object JWTUtils {
#Throws(Exception::class)
fun decoded(JWTEncoded: String): JWTBody {
val split = JWTEncoded.trim().split(".")
val mAdapt = moshi.adapter(JWTBody::class.java)
return mAdapt.fromJson(getJson(split[1])) ?: JWTBody()
}
#Throws(UnsupportedEncodingException::class)
private fun getJson(strEncoded: String): String {
val decodedBytes: ByteArray = Base64.decode(strEncoded, Base64.URL_SAFE)
return String(decodedBytes, charset("UTF-8"))
}
}
// JWT passed to the Utils
val decoded = JWTUtils.decoded(jwtString)

Retrieve Inheritance class full name from Kotlin Psi API

I’m trying to develop a codegen IDEA-Plugin. This plugin should analyze KtClass Inheritance and get all inheritance class full name (like com.example.config.TestConfig)
I have tried to find any useful information by viewing PsiViewer. I find that all
inheritance info of KtClass is stored in KtSuperTypeEntry, and I try my best to get full name of inheritance class.
for class Dest:
data class Dest(
val stringValue: String = "123",
override val stringConfig: String = "123",
override val iConfigStr: String = "123",
val b: B = B(),
val c: List<List<Set<Map<String, out Any?>>>> = listOf(),
val config: Config? = Config()
) : Config()
superTypeListEntry.typeAsUserType.referenceExpression.getReferencedName() -return-> "Config"
superTypeListEntry.importReceiverMembers() -return-> null
Seemingly SuperTypeListEntry just contain inheritance class simple name info.
I also try to find inheritance class full name by KtFile, but there is no idea when inheritance class was imported in this KtFile as wildcards:
fun KtSuperTypeListEntry.getType(ktFile: KtFile): String {
val simpleName = superTypeListEntry.text
// try to find by declared KtClass ...
ktFile.children.filterIsInstance<KtClass>().filter { it.name == simpleName }
// try to find by import ...
ktFile.importDirectives.filter { it.importPath.toString().contains(simpleName) }
// try to find by import wildcards ...
ktFile.importDirectives.filter { it.importPath.toString().endWith("*") }.forEach {
val split = it.importPath.split(".")
split.set(split.size - 1, simpleName)
val maybeFullName = split.joinToString(",") { it }
// confused on how to detect "maybeFullName" is correct ...
}
}
Question
How can I retrieve all inheritance class full name from Kotlin Psi API? Thank you!
After thousand of investigations and debugging, I find that it is possible to find a class's inheritance classes by BindingContext. BindingContext can analyze a TypeReference and find the reference of KotlinType. The code might be like this:
ktClass.superTypeListEntries.map { superTypeEntry ->
val typeReference = superTypeEntry.typeReference
val bindingContext = typeReference.analyze()
bindingContext.get(BindingContext.TYPE, typeReference)
}.forEach { kotlinType ->
val classId = kotlinType.constructor.declarationDescriptor.classId
val packageName = classId.packageFqName
val simpleName = classId.relativeClassName
// It can also get the generics of this class by KotlinType.arguments
val generics = kotlinType.arguments
}
Also, you can get super types full name of the class by KtLightClass, the code might be like this:
val ktLightClass = ktClass.toLightClass()
val superTypesFullName = ktLightClass?.supers?.forEach { superType ->
val packageName = superType.qualifiedName
val simpleName = superType.name
// you can get as KtClass by this, which can help you unify design of interface.
val ktClass = superType.kotlinOrigin
}

Map Key Values to Dataclass in Kotlin

how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...

How to instantiate class in Kotlin, if the class is parameter of function?

Im trying to make universal function in Kotlin, which can instantiate every time different model classes.
Class type is a parameter, to make instance from that class and fill it with data from Json object.
fun<T> foo() {
var myModel = T()
myModel.id = 2
myModel.name = ""
}
You can use an inline reified function in combination with an interface.
With reified you can access the real class of the generic type parameter.
You still not allowed to call the constructor directly, but reflection will work.
To assign the id and name, you need to define an interface, that all of your model classes are required to implement:
interface Model {
var id: Int?
var name: String?
}
inline fun <reified T : Model> createModel() : T {
val myModel = T::class.createInstance()
myModel.id = 2
myModel.name = ""
return myModel
}
A simple example:
class TestModel() : Model {
override var id: Int? = null
override var name: String? = null
}
fun main() {
val model: TestModel = createModel()
}
You cannot use T definition itself, pass class to the function instead.
import kotlin.reflect.KClass
open class Model {
var id: Int? = null
var name: String? = null
fun say() {
println("hello.")
}
}
class MyModel: Model()
fun<T: Model> foo(type: KClass<T>): T {
val myModel = type.java.newInstance()
myModel.id = 2
myModel.name = ""
return myModel
}
val mymodel = foo(MyModel::class)
mymodel.say() // hello.

Use a class from a list of generic interface

I am trying to implement a QueryBus. Basically, I want to register a list of QueryHandlers. Each QueryHandler implements a handle method defined by an interface. Each QueryHandler is associated to a Query. I want to be able to retrieve a QueryHandler using the Query and call handle on it.
The thing is the handle has to be generic because each QueryHandler handles a Query differently. They all take a dedicated Query and may return whatever they want.
interface Query<R>
interface QueryHandler<R, Q : Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
class ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
class GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = mapOf(
with(ListBooksQueryHandler()) {this.listenTo() to this},
with(GetBookQueryHandler()) {this.listenTo() to this}
)
val command = ListBooksQuery()
val result = queryHandlers[command::class.toString()].handle(command)
// Should print the list of BookDto
print(result)
}
I don't even know if its possible, to be honest.
UPDATE 1:
I changed the usage example in the main to show what I am really trying to do. The List was for (bad?) demonstration purpose. I want to store the QueryHandlers and retrieve them from a map.
Additional resources:
Here is what I really want to do:
https://gist.github.com/ValentinTrinque/76b7a32221884a46e657090b9ee60193
UPDATE I've read your gist and tried to come up with a solution that will provide a clean interface to the user of the QueryBusMiddleware.
Note that I used objects instead of classes for the QueryHandler implementations, which felt more natural to me (since there is only one possible entry in the map for each Query implementation).
interface Query<R>
interface QueryHandler<R, Q: Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
object ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
object GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = listOf(
ListBooksQueryHandler,
GetBookQueryHandler
)
val dispatcher: QueryBusMiddleware = QueryDispatcherMiddleware(queryHandlers)
// Calling query bus
val query = ListBooksQuery()
// Result should be List<BookDto>
val result = dispatcher.dispatch(query)
print(result)
}
interface QueryBusMiddleware {
fun <R, Q : Query<R>> dispatch(query: Q): R
}
class QueryDispatcherMiddleware constructor(handlers: List<QueryHandler<*, *>>) : QueryBusMiddleware {
private val handlers = HashMap<String, QueryHandler<*, *>>()
init {
handlers.forEach { handler -> this.handlers[handler.listenTo()] = handler }
}
override fun <R, Q : Query<R>> dispatch(query: Q): R {
val queryClass = query::class.toString()
val handler = handlers[queryClass] ?: throw Exception("No handler listen to the query: $queryClass")
return handler::class.members.find { it.name == "handle" }!!.call(handler, query) as R
}
}