I am making a library,
In one of the functionality, I receive an object and I have to perform an operation on fields and save them on a Map.
The object can have a field of type custom class which will again have fields and in that case, I'll need a nested hashmap. To do that I'll need to call my function recursively if the type of field in a custom class.
Now the problem is, how will I check if the type of field is a custom class or not, right now I am doing it by package name but have to make it general
private fun getAllFields(`object`: Any): MutableMap<String, Any> {
val map: MutableMap<String, Any> = HashMap()
val internalMap: MutableMap<String, Any> = HashMap()
for (field in `object`::class.java.declaredFields.toMutableList()) {
field.isAccessible = true
if (field.type.name.contains("com.example")) {
internalMap.putAll(getAllFields(field.get(`object`)))
}
As you're using reflection, you could introduce an annotation:
package org.your.library
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.RUNTIME)
annotation class YourCoolAnnotation
Which then can be used by the users of your library to annotate the classes that they want to be nested:
package com.example.libraryuser
#YourCoolAnnotation
class MyCustomClass
Then you can replace your:
if (field.type.name.contains("com.example")) {
With:
if (field.type.isAnnotationPresent(YourCoolAnnotation::class.java)) {
You could also specify the annotation to be only used on fields which would make this a lot more dynamic:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class YourCoolAnnotation
and then check if the field has the annotation and not the type itself:
if (field.isAnnotationPresent(YourCoolAnnotation::class.java)) {
Related
I am trying to create custom mapper in external class to map String to Integer. the map method is called successfully when it is defined in the mapper that uses it. but when I put the method in an external class, I get error that MapStruct cannot find the method:
error: Qualifier error. No method found annotated with #Named#value: [ MapperUtils and mapEnum ]. See https://mapstruct.org/faq/#qualifier for more info. #Mapping(target = "invoiceLanguage", source = "invoiceLanguage", qualifiedByName = {"MapperUtils", "mapEnum"})
This is the mapper abstract class. the commented code contains the custom mapping method that is called successfully. this method I want to externalize in order to be able to call it from multiple mappers
#Mapper(componentModel = "spring", uses = MapperUtils.class, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class CustomerAccountMapper {
public abstract ExistingCustomerAccountDto map(CustomerAccountDao dao);
public abstract CustomerAccountDao map(NewCustomerAccountRequest request);
#Mapping(target = "invoiceLanguage", source = "invoiceLanguage",
qualifiedByName = {"MapperUtils", "mapEnum"})
public abstract CustomerAccountDao map(UpdateCustomerAccountRequest request);
// works fine when method is in mapper class
// #Named("mapEnum")
// Integer mapEnum(String input) {
// if ("null".equalsIgnoreCase(input)) {
// return null;
// }
// return Integer.valueOf(input);
// }
}
I have followed the instructions in the user guide:
Created external class with custom map method. added #Named to class and method
added uses to #Mapper annotation of mapper class
added qualifiedByName to #Mapping annotation on method of mapper
This is my external mapper class
#Component
#Named("MapperUtils")
#SuppressWarnings("unused")
public class MapperUtils {
#Named("mapEnum")
Integer mapEnum(String input) {
if ("null".equalsIgnoreCase(input)) {
return null;
}
return Integer.valueOf(input);
}
}
what am I missing?
Looks like when implementing it with #Named and qualifiedByName will work only if MapperUtils will be in the exact same package as CustomerAccountMapper
Mapstruct documentation mentions that working with qualifiedByName is not the best way as its not very predictable
Although the used mechanism is the same, the user has to be a bit more
careful. Refactoring the name of a defined qualifier in an IDE will
neatly refactor all other occurrences as well. This is obviously not
the case for changing a name.
Also from qualifiedByName Java docs
Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large number of qualifiers as no custom annotation types are needed.
So, I have this annotation class
#Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class MyTestAnnotation(val text: String)
And I'm using this like this
interface MyTestInterface {
#MyTestAnnotation("temp")
var tempString: String
}
This is how I'm instantiating my interface using reflection.
fun <T> with(myInterface: Class<T>): T {
return Proxy.newProxyInstance(
myInterface.classLoader,
arrayOf<Class<*>>(myInterface),
invocationHandler
) as T
}
private val invocationHandler = InvocationHandler { _, method, args ->
Log.e("Called method:", method.name) // setTempString
Log.e("declaredAnnotations", method.declaredAnnotations.size.toString()) // 0
Log.e("annotations", method.annotations.size.toString()) // 0
Log.e("args", args.size.toString()) // 1
}
I'm calling it like this
val myInterface = with(MyTestInterface::class.java)
myInterface.tempString = "123"
I'm not able to access the member text of the annotation class because in my invocationHandler I'm not getting the annotation(as you can see both the arrays are of length zero).
My question: Is there any way I can copy the annotation to the getter and setter so I can get access to the data which I put in the annotation?
You can specify the use-site target of the annotation. For example, if you want to annotate both the setter and the getter you can do:
#set:YourAnnotation
#get:YourAnnotation
val aProperty: AType
Official documentation: https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets
My project has following setup
#Module
abstract class CoreModule {
#Provides
#Named("text1")
fun textOne() = "text1"
#Provides
#Named("text2")
fun textTwo() = "text2"
}
#Component(modules=[CoreModule::class])
interface CoreComponent{
#Named("text1")
fun textOne(): String
}
#Component(
dependencies = [CoreComponent::class]
modules=[AppModule::class]
)
interface AppComponent()
#Module()
class AppModule {
fun getStringDouble(#Named("text1") text1: String) = text1 + text1
}
Here I have 2 components CoreComponent which provides dependencies to AppComponent. Now I want to provide only text1 to AppComponent.
Since I have added #Named("text1") in CoreComponent to denote which string to provide to AppComponent. It forces me to use #Named("text1") in AppModule as well, which I don't want.
How can I create provision method in CoreComponent to provide only text1 to AppComponent in such a way, that I don't have to use #Named everywhere in AppComponent
The simplest solution would probably be to simply bind/provide it under a different key, a plain String in your example.
#Binds // bind it without any qualifier
fun plainText1(#Named("text1") text1: String) : String
If you do have multiple values for the same type then qualifiers would probably be easier to use in the long run though, since they allow you proper naming of the keys used.
Keep in mind that you can also create your own qualifiers and don't have to use #Named, it's just available by default.
An alternative to your setup altogether would be to use (Sub)components for encapsulation which would allow you to only ever return the value produced without binding the whole component dependency.
you can remove #Named("<name>") from Provider method in CoreModule and CoreComponent.
instead, you can create a custom Qualifier annotation like this
#Qualifier
#Documented
#Retention(RetentionPolicy.RUNTIME)
annotation class TextTwoName {
}
and do following changes
In CoreModule, use #TextTwoName in textTwo() for instead of #Named("text2") and remove #Named("text1") from fun textOne() = "text1"
In CoreComponent, remove #Named("text1") from fun textOne(): String while name function name textOne doesn't matter here only return type matters. it will take fun textOne() = "text1" from CoreModule
If you want to expose fun textTwo() = "text2" then you can add #TextTwoName annotation in the CoreComponent's fun textOne():String method.
I am about to make a service of mine generic. However I fail to do so when trying to pass a generic Kotlin type T to a Java method that expects a class. Using normal types I'd do it like MyClass::class.java. For the generic type I do T::class.java. This however seems not to be valid.
Cannot use 'T' as reified type parameter. Use a class instead.
Happening here return mongoTemplate.aggregate(resolvedDocument, T::class.java).mappedResults[0]
Service:
#Service
class DocumentAggregator<T: Output>(
#Autowired
private val mongoTemplate: MongoTemplate
) {
fun <S: DocumentEntity>aggregate(document: S): T? {
val resolvedDocument: TypedAggregation<DocumentEntity> = // logic
return mongoTemplate.aggregate(resolvedDocument, T::class.java).mappedResults[0]
}
}
You should try adding the reified keyword to the generic parameter, like this:
class DocumentAggregator<reified T: Output>
That ways the class will be present at runtime. Like when you added an additional Class<T> parameter, just with the nice Kotlin syntax sugar.
EDIT:
Regarding the comments the question would be if you need the generics on the class. What compiles (thanks to Willie for pointing out the mistake) would be:
class Output
class DocumentAggregator(
private val mongoTemplate: Any?
) {
inline fun <S, reified T: Output>aggregate(document: S): T? {
return null
}
}
First Post. Absolute Beginner. Be kind
I am playing arround with quarkus and kotlin.
I have this kotlin entity class:
#Entity
data class Fruit (
var name: String = "",
var description: String = ""
) : PanacheEntity()
I have this Resource Class based on tutorials in Java:
#Path("/fruits")
#ApplicationScoped
public class FruitJResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Fruit> getAll() {
return Fruit.listAll();
}
}
Everything fine here, Fruit inherits from PanacheEntityBase, i can access listAll()
However,
Same Class in Kotlin does not:
#Path("/fruits")
#ApplicationScoped
class FruitResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
fun getAll(): List<Fruit> = Fruit.listAll()
}
Now i learned allready, that this is probably due kotlin not beeing able to inherit static methods from Super Class.
I read, that i should call the static method direct from the superclass, but this won't work here.
So I need a suggestion for a possible workaround.
The only solution for kotlin and scala languages for now (1.4.1) is to use the Repository pattern :
see documentation: https://quarkus.io/guides/hibernate-orm-panache#solution-2-using-the-repository-pattern
This is due to referenced issue github.com/quarkusio/quarkus/issues/4394.
So if using Kotlin, you simply have to define a new FruitRepository
#ApplicationScoped
class FruitRepository: PanacheRepository<Fruit> {
fun all(): List<Fruit> = findAll(Sort.by("name")).list<Fruit>()
}
Quarkus has released an extension which brings Kotlin support to panache (I think it is still in preview).
in Gradle (if you're using Gradle for your project) you'll need to add the dependencie implementation 'io.quarkus:quarkus-hibernate-orm-panache-kotlin'
To define your "static" methods (Kotlin uses Companion objects to do stuff with static methods) you'll need to define a companion object like this:
#Entity
open class Category : PanacheEntityBase {
#Id
#GeneratedValue
lateinit var id: UUID
// ...
companion object : PanacheCompanion<Category, UUID> {
fun findByName(name: String) = find("name", name).firstResult()
fun findActive() = list("active", true)
fun deleteInactive() = delete("active", false)
}
}
for more information you can check out the official docs:
https://quarkus.io/guides/hibernate-orm-panache-kotlin
Be warned if you use unit tests: at least for me the panache-mock extension is not working with the kotlin version of panache.