Get value from annotation failed - kotlin

This is annotation definition:
#Target(AnnotationTarget.PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
#MustBeDocumented
annotation class MyAnno(val desc: String, val comment: String) { }
And below is where the MyAnno used:
class MyAnnoUser {
#MyAnno(desc = "name", comment = "name comment")
lateinit var name: String
#MyAnno(desc = "age", comment = "age comment")
var age: Int = 0
#MyAnno(desc = "money", comment = "money comment")
var money: Double = 0.0
#MyAnno(desc = "gender", comment = "gender comment")
var gender: Boolean = false
override fun toString(): String {
return "(name: $name; age: $age; money: $money; gender: ${if (gender) "men" else "women"})"
}
}
Here's code to read the value in MyAnno:
class MyAnnoExpression(val obj: Any, val context: Context) {
val numTypeSet = setOf("Int", "Double", "Byte")
fun expression() {
val clazz = obj::class
clazz.declaredMemberProperties.forEach { prop ->
val mutableProp = try {
prop as KMutableProperty<*>
} catch (e: Exception) {
null
} ?: return#forEach
val desc = mutableProp.findAnnotation<MyAnno>()
desc?.let {
val propClassName = mutableProp.returnType.toString().removePrefix("kotlin.")
when (propClassName) {
in numTypeSet -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toNum(propClassName))
"String" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String))
"Boolean" -> mutableProp.setter.call(obj, (readProp(it, context) as kotlin.String).toBoolean())
}
}
}
}
private fun readProp(value: MyAnno, context: Context): Any? {
val prop = Properties()
val input = context.assets.open("app.properties")
prop.load(InputStreamReader(input, "utf-8"))
return prop.get(value.desc)
}
}
Now the Debugger shows me following info of value in readProp(...) function:
#com.demo.basekotlin.MyAnno(comment=age comment, desc=age)
But i got exception when read desc from value:
An exception occurs during Evaluate Expression Action : org.jetbrains.eval4j.VOID_VALUE cannot be cast to org.jetbrains.eval4j.AbstractValue
I can't find any thing wrong in my code, is there another program setting needed?

As I understand you just want to see annotation value for given property.
First, let's declare an annotation.
#Target(PROPERTY)
#Retention(AnnotationRetention.RUNTIME)
annotation class PropertyAnnotation(val desc: String)
Container:
class Container {
#PropertyAnnotation("Name")
var name: String? = null
#PropertyAnnotation("Age")
var age: Int = -1
var notAnnotatedProperty: String = "not annotated"
}
And finally, code responsible for get all declared properties, then find a properties annotated as PropertyAnnotation, cast it to it, and get value from it.
fun main() {
val container = Container()
container::class.declaredMemberProperties.forEach { property ->
(property.annotations.find {
it is PropertyAnnotation
} as? PropertyAnnotation)?.let {
println("Property: `$property` is ${it.desc}")
}
}
}
Output:
Property: `var Container.age: kotlin.Int` is Age
Property: `var Container.name: kotlin.String?` is Name
Kind ugly. But, let's use more Kotlin pro-dev-features.
Let's create extension function for any not-null type which returns all member property of given type:
inline fun <reified T : Any> Any.getMemberProperty(): List<T> {
return this::class.declaredMemberProperties.mapNotNull { prop ->
(prop.annotations.find { ann -> ann is T }) as? T
}
}
And now usage:
fun main() {
val container = Container()
container.getMemberProperty<PropertyAnnotation>().forEach {
println(it.desc)
}
}

Related

Kotlin - Ktor - How to handle Optional API resource fields in PATCH calls?

When implementing a REST API with Ktor (and Kotlin), it supports the optional field handling of Kotlin. Which works for POST and GET, but what about PATCH (update)?
For example, you have the following resource:
#Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
So all MyAddress fields are optional (with a default value).
When you create an address with POST:
"line1" : "line1",
"country" : "XX"
and you than want to remove the country with a PATCH:
"country" : null
the end result of the resource should be:
"line1" : "line1"
But how can you determine this by parsing the json of the PATCH request? Because there is no way, as far as I know, to determine if it was null by default, or submitted.
Furthermore, the default null value for the MyAddress is required, because else the parsing will not work.
Code example:
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
#kotlinx.serialization.Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddress>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringPATCH = "{\"country\":null}"
println("JSON string is: $jsonStringPATCH")
val myAddressPatch = Json.decodeFromString<MyAddress>(jsonStringPATCH)
println("MyAddress object: $myAddressPatch")
}
I tried to add Optional<String>? as well, but it complains about missing serialization of Optional, and preferably I do not want to make all my data var's Optionals.
Note: I am looking for a more structured solution, that also works with all other resources in the api (10+ classes).
A second solution, based on Aleksei's example:
#Serializable
data class Address2(val line1: OptionalValue<String> = Undefined, val country: OptionalValue<String> = Undefined)
#Serializable(with = OptionalValueSerializer::class)
sealed interface OptionalValue<out T>
object Undefined: OptionalValue<Nothing> {
override fun toString(): String = "Undefined"
}
object Absent: OptionalValue<Nothing> {
override fun toString(): String = "Absent"
}
class WithValue<T>(val value: T): OptionalValue<T> {
override fun toString(): String = value.toString()
}
open class OptionalValueSerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalValue<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalValue<T> {
return try {
WithValue(valueSerializer.deserialize(decoder))
} catch (cause: SerializationException) {
Absent
}
}
override fun serialize(encoder: Encoder, value: OptionalValue<T>) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> valueSerializer.serialize(encoder, value.value)
}
}
}
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<Address2>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<Address2>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country is Absent || myAddressUpdate.country is WithValue) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
Output is:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: Address2(line1=street, country=GB)
JSON string is: {"country":null}
MyAddress object: Address2(line1=Undefined, country=Absent)
Update country: Absent
You can use a sealed interface for a part of an address to represent undefined value, absence of value, and actual value. For this interface, you need to write a serializer that will encode and decode values accordingly to your logic. I'm not good at the kotlinx.serialization framework so I wrote an example as simple as possible.
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
fun main() {
embeddedServer(Netty, port = 4444) {
install(ContentNegotiation) {
json()
}
routing {
post {
val address = call.receive<Address>()
println(address)
}
}
}.start()
}
#Serializable
data class Address(val line1: MyValue = Undefined, val country: MyValue = Undefined)
#Serializable(with = AddressValueSerializer::class)
sealed interface MyValue
object Undefined: MyValue {
override fun toString(): String = "Undefined"
}
object Absent: MyValue {
override fun toString(): String = "Absent"
}
class WithValue(val value: String): MyValue {
override fun toString(): String = value
}
object AddressValueSerializer: KSerializer<MyValue> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("AddressValue", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MyValue {
return try {
WithValue(decoder.decodeString())
} catch (cause: SerializationException) {
Absent
}
}
#OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: MyValue) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> { encoder.encodeString(value.value) }
}
}
}
With some help from medium.com, I came to the following solution:
#Serializable(with = OptionalPropertySerializer::class)
open class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>() {
override fun toString(): String {
return value.toString()
}
}
fun isPresent() : Boolean {
return this is Present
}
fun isNotPresent(): Boolean {
return this is NotPresent
}
fun isEmpty(): Boolean {
return (this is Present) && this.value == null
}
fun hasValue(): Boolean {
return (this is Present) && this.value != null
}
override fun toString(): String {
if(this is NotPresent) {
return "<NotPresent>"
}
return super.toString()
}
}
open class OptionalPropertySerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalProperty<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalProperty<T> =
OptionalProperty.Present(valueSerializer.deserialize(decoder))
override fun serialize(encoder: Encoder, value: OptionalProperty<T>) {
when (value) {
is OptionalProperty.NotPresent -> {}
is OptionalProperty.Present -> valueSerializer.serialize(encoder, value.value)
}
}
}
#Serializable
private data class MyAddressNew(
var line1: OptionalProperty<String?> = OptionalProperty.NotPresent,
var line2: OptionalProperty<String?> = OptionalProperty.NotPresent,
var city: OptionalProperty<String?> = OptionalProperty.NotPresent,
var postal_code: OptionalProperty<String?> = OptionalProperty.NotPresent,
var state: OptionalProperty<String?> = OptionalProperty.NotPresent,
var country: OptionalProperty<String?> = OptionalProperty.NotPresent,
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddressNew>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<MyAddressNew>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country.isPresent()) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
This prints:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: MyAddressNew(line1=street, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=GB)
JSON string is: {"country":null}
MyAddress object: MyAddressNew(line1=<NotPresent>, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=null)
Update country: null

Data class toString name clash property / construction arg

Newby in Kotlin, I'm trying to implement a simple data class with constraint validation in fields.
This works great, but my solution is suboptimal since it exposes the private variables names defined in the class' definition in the toString representation, where I would prefer to have the properties:
data class MutablePointKt(private val _x: Int = 0, private val _y: Int = 0) {
private fun validatePositiveOrZero(some:Int) {
Validate.isTrue(some >= 0, "negative coordinate provided: $some")
}
var x: Int = 0
get() {
println(" > getting x: $field")
return field
}
set(value) {
validatePositiveOrZero(value)
field = value
}
var y: Int = 0
get() {
println(" > getting y: $field")
return field
}
set(value) {
validatePositiveOrZero(value)
field = value
}
init {
this.x = _x;
this.y = _y;
}
}
println(MutablePointKt(1, 2)) // prints "MutablePointKt(_x=1, _y=2)". how to print "MutablePointKt(x=1, y=2)" ?
Thank you !
EDIT:
I have a solution with
override fun toString(): String = ToStringBuilder.reflectionToString(this, KotlinToStringStyle()) and
class KotlinToStringStyle : ToStringStyle() {
private fun isFiltered(s: String?) = s?.startsWith("_") == true
override fun appendFieldStart(buffer: StringBuffer?, fieldName: String?) {
if (!isFiltered(fieldName))
super.appendFieldStart(buffer, fieldName)
}
override fun appendDetail(buffer: StringBuffer?, fieldName: String?, any: Any) {
if (!isFiltered(fieldName))
super.appendDetail(buffer, fieldName, any)
}
override fun appendFieldEnd(buffer: StringBuffer?, fieldName: String?) {
if (!isFiltered(fieldName))
super.appendFieldEnd(buffer, fieldName)
}
}
... but this is rather overkill, I would prefer a concise solution aka "the Kotlin way"

Kotlinx Serialization - Custom serializer to ignore null value

Let's say I'm having a class like:
#Serializable
data class MyClass(
#SerialName("a") val a: String?,
#SerialName("b") val b: String
)
Assume the a is null and b's value is "b value", then Json.stringify(MyClass.serializer(), this) produces:
{ "a": null, "b": "b value" }
Basically if a is null, I wanted to get this:
{ "b": "b value" }
From some research I found this is currently not doable out of the box with Kotlinx Serialization so I was trying to build a custom serializer to explicitly ignore null value. I followed the guide from here but couldn't make a correct one.
Can someone please shed my some light? Thanks.
You can use explicitNulls = false
example:
#OptIn(ExperimentalSerializationApi::class)
val format = Json { explicitNulls = false }
#Serializable
data class Project(
val name: String,
val language: String,
val version: String? = "1.3.0",
val website: String?,
)
fun main() {
val data = Project("kotlinx.serialization", "Kotlin", null, null)
val json = format.encodeToString(data)
println(json) // {"name":"kotlinx.serialization","language":"Kotlin"}
}
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#explicit-nulls
Use encodeDefaults = false property in JsonConfiguration and it won't serialize nulls (or other optional values)
Try this (not tested, just based on adapting the example):
#Serializable
data class MyClass(val a: String?, val b: String) {
#Serializer(forClass = MyClass::class)
companion object : KSerializer<MyClass> {
override val descriptor: SerialDescriptor = object : SerialClassDescImpl("MyClass") {
init {
addElement("a")
addElement("b")
}
}
override fun serialize(encoder: Encoder, obj: MyClass) {
encoder.beginStructure(descriptor).run {
obj.a?.let { encodeStringElement(descriptor, 0, obj.a) }
encodeStringElement(descriptor, 1, obj.b)
endStructure(descriptor)
}
}
override fun deserialize(decoder: Decoder): MyClass {
var a: String? = null
var b = ""
decoder.beginStructure(descriptor).run {
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break#loop
0 -> a = decodeStringElement(descriptor, i)
1 -> b = decodeStringElement(descriptor, i)
else -> throw SerializationException("Unknown index $i")
}
}
endStructure(descriptor)
}
return MyClass(a, b)
}
}
}
Since I was also struggling with this one let me share with you the solution I found that is per property and does not require to create serializer for the whole class.
class ExcludeIfNullSerializer : KSerializer<String?> {
override fun deserialize(decoder: Decoder): String {
return decoder.decodeString()
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("ExcludeNullString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: String?) {
if (value != null) {
encoder.encodeString(value)
}
}
}
will work as expected with the following class
#Serializable
class TestDto(
#SerialName("someString")
val someString: String,
#SerialName("id")
#EncodeDefault(EncodeDefault.Mode.NEVER)
#Serializable(with = ExcludeIfNullSerializer::class)
val id: String? = null
)
Note the #EncodeDefault(EncodeDefault.Mode.NEVER) is crucial here in case you using JsonBuilder with encodeDefaults = true, as in this case the serialization library will still add the 'id' json key even if the value of id field is null unless using this annotation.
JsonConfiguration is deprecated in favor of Json {} builder since kotlinx.serialization 1.0.0-RC according to its changelog.
Now you have to code like this:
val json = Json { encodeDefaults = false }
val body = json.encodeToString(someSerializableObject)
As of now, for anyone seeing this pos today, default values are not serialized (see https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#defaults-are-not-encoded-by-default)
So you simply add to set a default null value, and it will not be serialized.

How to fix wrong constructor parameters in kotlin

I have an interesting error with reflections in kotlin.
So, im using 'argTypes' method for getting all parameter type of args.
private fun argTypes(vararg args: Any): Array<Class<*>> {
val argTypes = ArrayList<Class<*>>()
args.forEach { argTypes.add(it::class.java) }
return argTypes.toTypedArray()
}
Im using it with like that:
fun <T> newInstance(clazz: Class<*>, argTypes: Array<Class<*>>, vararg args: Any): T {
return clazz.getDeclaredConstructor(*argTypes).newInstance(*args) as T
}
In the end:
ReflectionUtil.instance.newInstance<IBossBar>(
PacketBossBar1_13_R2::class.java,
TextComponent("asd"),Color.BLUE,Style.PROGRESS,100F)
I use a float parameters thats '100F'.
When i use that method, the type is going to be java.lang.Float but my 'PacketBossBar1_13_R2' constructor has a float parameters like that:
constructor(
message: TextComponent,
color: Color,
style: Style,
progress: Float
): this(ComponentSerializer.toString(message), color, style, progress)
When i getting the constructor as a manual, its return
public io.github.utsukushihito.utsutil.nms.v1_13_R2.PacketBossBar1_13_R2(net.md_5.bungee.api.chat.TextComponent,io.github.utsukushihito.utsutil.api.bossbar.enums.Color,io.github.utsukushihito.utsutil.api.bossbar.enums.Style,float)
When i use automatic way its returning NoSucMethodException like that:
java.lang.NoSuchMethodException: io.github.utsukushihito.utsutil.nms.v1_13_R2.PacketBossBar1_13_R2.<init>(net.md_5.bungee.api.chat.TextComponent, io.github.utsukushihito.utsutil.api.bossbar.enums.Color, io.github.utsukushihito.utsutil.api.bossbar.enums.Style, java.lang.Float)
Also my ReflectionUtil and PackateBossBar classes:
package io.github.utsukushihito.utsutil.api.misc
import org.bukkit.Bukkit
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.util.*
import java.util.stream.Collectors
class ReflectionUtil {
private val nmsVersion: String
get() = nms().split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[3]
val craftBukkitVersion: String
get() = cb().split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[3]
private fun nms(): String {
return exec<Any>(Bukkit.getServer(), "getServer").javaClass.getPackage().name
}
private fun cb(): String {
return Bukkit.getServer().javaClass.getPackage().name
}
fun getPackageName(nmsObject: Any): String {
return nmsObject.javaClass.getPackage().name
}
fun getBukkitClass(craftObject: Any): Class<*> {
var clazz: Class<*> = craftObject.javaClass
while (clazz.canonicalName.contains(".craftbukkit.")) {
clazz = clazz.superclass
}
return clazz
}
fun getCustomBukkitClass(className: String): Class<*> {
return Class.forName("org.bukkit.craftbukkit.$nmsVersion.$className")
}
fun getNMSClass(name: String): Class<*> {
return Class.forName("net.minecraft.server.$nmsVersion.$name")
}
fun <T> execStatic(clazz: Class<*>, methodName: String, vararg args: Any): T {
val method = getMethod(clazz, methodName, *argTypes(*args))
val wasAccessible = method.isAccessible
method.isAccessible = true
try {
return method.invoke(null, *args) as T
} finally {
method.isAccessible = wasAccessible
}
}
fun <T> execStatic(clazz: Class<*>, methodName: String, argTypes: Array<Class<*>>, vararg args: Any): T {
val method = getMethod(clazz, methodName, *argTypes)
val wasAccessible = method.isAccessible
method.isAccessible = true
try {
return method.invoke(null, *args) as T
} finally {
method.isAccessible = wasAccessible
}
}
fun <T> exec(obj: Any, methodName: String, argTypes: Array<Class<*>>, vararg args: Any): T {
val aClass = obj.javaClass
val method = getMethod(aClass, methodName, *argTypes)
val wasAccessible = method.isAccessible
method.isAccessible = true
try {
return method.invoke(obj, *args) as T
} finally {
method.isAccessible = wasAccessible
}
}
fun getMethod(aClass: Class<*>, methodName: String, vararg argTypes: Class<*>): Method {
return aClass.getDeclaredMethod(methodName, *argTypes)
}
fun findMethod(aClass: Class<*>, returnType: Class<*>, vararg argTypes: Class<*>): Method {
return findMethods(aClass, returnType, *argTypes)[0]
}
fun findMethods(aClass: Class<*>, returnType: Class<*>, vararg argTypes: Class<*>): List<Method> {
val methods = ArrayList<Method>()
for (m in aClass.declaredMethods) {
if (m.returnType == returnType && m.parameterTypes.size == argTypes.size) {
val mLookup = aClass.getMethod(m.name, *argTypes)
if (mLookup != null) methods.add(mLookup)
}
}
return methods
}
fun <T> exec(obj: Any, methodName: String, vararg args: Any): T {
return exec(obj, methodName, argTypes(*args), *args) as T
}
fun <T> getField(clazz: Class<*>, fieldName: String): T {
val field = getFieldFromClass(clazz, fieldName)
val wasAccessible = field.isAccessible
field.isAccessible = true
try {
return field.get(null) as T
} finally {
field.isAccessible = wasAccessible
}
}
fun <T> getField(obj: Any, fieldName: String): T {
val field = getFieldInternal(obj, fieldName)
val wasAccessible = field.isAccessible
field.isAccessible = true
try {
return field.get(obj) as T
} finally {
field.isAccessible = wasAccessible
}
}
fun getFieldInternal(obj: Any, fieldName: String): Field {
return getFieldFromClass(obj.javaClass, fieldName)
}
fun getFieldFromClass(aClass: Class<*>, fieldName: String): Field {
return try {
aClass.getDeclaredField(fieldName)
} catch (e: NoSuchFieldException) {
try {
aClass.getField(fieldName)
} catch (e1: NoSuchFieldException) {
getFieldFromClass(aClass.superclass, fieldName)
}
}
}
fun setField(obj: Any, fieldName: String, field: Any?) {
val declaredField = getFieldInternal(obj, fieldName)
val wasAccessible = declaredField.isAccessible
declaredField.isAccessible = true
try {
declaredField.set(obj, field)
} finally {
declaredField.isAccessible = wasAccessible
}
}
fun <T> newInstance(clazz: Class<*>, argTypes: Array<Class<*>>, vararg args: Any): T {
return clazz.getDeclaredConstructor(*argTypes).newInstance(*args) as T
}
fun <T> newInstance(clazz: Class<*>, vararg args: Any): T {
return newInstance(clazz, argTypes(*args), *args)
}
fun <T> newInstance(className: String, vararg args: Any): T {
return newInstance(className, argTypes(*args), *args)
}
private fun argTypes(vararg args: Any): Array<Class<*>> {
val argTypes = ArrayList<Class<*>>()
args.forEach { argTypes.add(it::class.java) }
return argTypes.toTypedArray()
}
fun dumpMethods(aClass: Class<*>): List<String> {
val methods = aClass.declaredMethods
val methodDescriptions = ArrayList<String>()
val version = nmsVersion
for (m in methods) {
var parmString = Arrays.toString(Arrays.stream(m.parameterTypes).map<String>{ it.name }.toArray())
parmString = parmString.substring(1, parmString.length - 1)
var description = ((if (Modifier.isPublic(m.modifiers)) "public " else if (Modifier.isPrivate(m.modifiers)) "private " else "")
+ (if (Modifier.isStatic(m.modifiers)) "static " else "")
+ m.returnType + " " + m.name
+ "(" + parmString + ")")
description = description
.replace("class net.minecraft.server.$version.".toRegex(), "")
.replace("net.minecraft.server.$version.".toRegex(), "")
.replace("java.lang.".toRegex(), "")
methodDescriptions.add(description)
}
val list = ArrayList<String>()
list.add(aClass.toString().replace("class net.minecraft.server.$version.".toRegex(), "")
.replace("net.minecraft.server.$version.".toRegex(), "")
.replace("java.lang.".toRegex(), "") + ":")
list.addAll(methodDescriptions.stream().sorted { obj, anotherString -> obj.compareTo(anotherString) }.collect(Collectors.toList()))
return list
}
companion object {
val instance = ReflectionUtil()
}
}
package io.github.utsukushihito.utsutil.nms.v1_13_R2
import io.github.utsukushihito.utsutil.api.bossbar.addBossBarForPlayer
import io.github.utsukushihito.utsutil.api.bossbar.enums.Color
import io.github.utsukushihito.utsutil.api.bossbar.enums.Property
import io.github.utsukushihito.utsutil.api.bossbar.enums.Style
import io.github.utsukushihito.utsutil.api.bossbar.removeBossBarForPlayer
import io.github.utsukushihito.utsutil.api.misc.ReflectionUtil
import io.github.utsukushihito.utsutil.api.nms.IBossBar
import net.md_5.bungee.api.chat.TextComponent
import net.md_5.bungee.chat.ComponentSerializer
import net.minecraft.server.v1_13_R2.BossBattle
import net.minecraft.server.v1_13_R2.IChatBaseComponent
import net.minecraft.server.v1_13_R2.PacketPlayOutBoss
import org.bukkit.Location
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer
import org.bukkit.entity.Player
import java.util.*
class PacketBossBar1_13_R2(
private var message: String,
private var color: Color,
private var style: Style,
private var progress: Float,
vararg properties: Property
) : IBossBar{
private val receivers = ArrayList<Player>()
private val uuid = UUID.randomUUID()
private var darkenSky: Boolean = false
private var playMusic: Boolean = false
private var createFog: Boolean = false
private var visible: Boolean = false
constructor(
message: TextComponent,
color: Color,
style: Style,
progress: Float,
vararg properties: Property
): this(ComponentSerializer.toString(message), color, style, progress, *properties)
// I Try to run this cotr with the reflection util.
constructor(
message: TextComponent,
color: Color,
style: Style,
progress: Float
): this(ComponentSerializer.toString(message), color, style, progress)
init {
setMessage(message)
setProgress(progress)
properties.forEach {
setProperty(it,true)
}
}
override fun getPlayers(): Collection<Player> {
return ArrayList(this.receivers)
}
override fun addPlayer(player: Player) {
if (!receivers.contains(player)) {
receivers.add(player)
sendPacket(PacketPlayOutBoss.Action.ADD, player)
player.addBossBarForPlayer(this)
}
}
override fun removePlayer(player: Player) {
if (receivers.contains(player)) {
receivers.remove(player)
sendPacket(PacketPlayOutBoss.Action.REMOVE, player)
player.removeBossBarForPlayer(this)
}
}
override fun getColor(): Color {
return color
}
override fun setColor(color: Color) {
if (color != this.color) {
this.color = color;
sendPacket(PacketPlayOutBoss.Action.UPDATE_STYLE, null);
}
}
override fun getStyle(): Style {
return style
}
override fun setStyle(style: Style) {
if (style != this.style) {
this.style = style
sendPacket(PacketPlayOutBoss.Action.UPDATE_STYLE, null)
}
}
override fun setProperty(property: Property, flag: Boolean) {
when (property) {
Property.DARKEN_SKY -> darkenSky = flag
Property.PLAY_MUSIC -> playMusic = flag
Property.CREATE_FOG -> createFog = flag
}
sendPacket(PacketPlayOutBoss.Action.UPDATE_PROPERTIES, null)
}
override fun setMessage(message: String) {
if (!message.startsWith("{") || !message.endsWith("}")) {
throw IllegalArgumentException("Invalid JSON")
}
if (message != this.message) {
this.message = message
sendPacket(PacketPlayOutBoss.Action.UPDATE_NAME, null)
}
}
override fun getMessage(): String {
return message;
}
override fun setVisible(flag: Boolean) {
if (flag != this.visible) {
this.visible = flag
sendPacket(if (flag) PacketPlayOutBoss.Action.ADD else PacketPlayOutBoss.Action.REMOVE, null)
}
}
override fun isVisible(): Boolean {
return visible
}
override fun getProgress(): Float {
return progress
}
override fun setProgress(progress: Float) {
if (progress > 1) this.progress = progress / 100F
if (progress != this.progress) {
this.progress = progress
sendPacket(PacketPlayOutBoss.Action.UPDATE_PCT, null)
}
}
private fun sendPacket(action: PacketPlayOutBoss.Action, player: Player?) {
try {
val packet = PacketPlayOutBoss()
val ref = ReflectionUtil.instance
ref.setField(packet,"a",uuid)
ref.setField(packet,"b",action)
ref.setField(packet,"c",IChatBaseComponent.ChatSerializer.a(message)?: "")
ref.setField(packet,"d",progress)
ref.setField(packet,"e", BossBattle.BarColor.a(color.id))
ref.setField(packet,"f",BossBattle.BarStyle.a(style.id))
ref.setField(packet,"g",darkenSky)
ref.setField(packet,"h",playMusic)
ref.setField(packet,"i",createFog)
if (player != null) {
(player as CraftPlayer).handle.playerConnection.sendPacket(packet)
} else {
for (player1 in this.getPlayers()) {
(player1 as CraftPlayer).handle.playerConnection.sendPacket(packet)
}
}
} catch (e: ReflectiveOperationException) {
throw RuntimeException(e)
}
}
override fun getMaxHealth(): Float {
return 100F
}
override fun setHealth(percentage: Float) {
setProgress(percentage / 100F)
}
override fun getHealth(): Float {
return getProgress() * 100F
}
override fun getReceiver(): Player {
throw UnsupportedOperationException()
}
override fun getLocation(): Location {
throw UnsupportedOperationException()
}
override fun updateMovement() {
throw UnsupportedOperationException()
}
}```
You can fix this specific case by doing
private fun argTypes(vararg args: Any): Array<Class<*>> {
val argTypes = args.map { it::class.javaPrimitiveType ?: it::class.java }
return argTypes.toTypedArray()
}
but then this wouldn't find a constructor which expects java.lang.Float.
Simply put: if you know the actual arguments for a constructor/method, there are many options for the types in the signature, so trying just one (as fun argTypes does) can't possibly work in general. In addition to the problem you ran into, the declared parameter type could be a supertype of an argument's class, the constructor could have varargs, etc.
A possible solution is just to get all constructors, feed your arguments to them and use the first one which doesn't throw an exception. You still need to be careful about varargs. Whether the performance cost is acceptable will depend on your use-case.

Instantiating type T in Kotlin

In short, I would like to omit the repeated getT() in the example below. I have read Instantiating a generic type in Kotlin, but what does it mean 'to take () -> T as a parameter'? How can I apply that to below?
interface Food
{
var isHeated:Boolean;
var name:String;
}
abstract class Cooker<T:Food>
{
abstract fun getT():T;
abstract fun enhance(t:T);
fun cook(): T
{
var food = getT();
food.isHeated = true;
food.name = "heated " + food.name;
enhance(food);
return food;
}
}
class PotatoChip:Food
{
override var isHeated = false;
override var name = "potato chip";
}
class PotatoChipCooker:Cooker<PotatoChip>()
{
override fun getT(): PotatoChip {
return PotatoChip();
}
override fun enhance(t:PotatoChip)
{
t.name = "salted " + t.name;
}
}
class Pancake:Food
{
override var isHeated = false;
override var name = "pancake";
}
class PancakeCooker:Cooker<Pancake>()
{
override fun getT(): Pancake {
return Pancake();
}
override fun enhance(t:Pancake)
{
t.name = t.name + " coated with maple syrup";
}
}
fun main(args: Array<String>)
{
val result = PotatoChipCooker().cook();
println(result.name);
val result2 = PancakeCooker().cook();
println(result2.name);
}
You could make the initialization function part of the primary constructor. As a result, implementing classes will have to pass a function that specifies how the corresponding type is being created:
abstract class Cooker<T : Food>(private val initT: () -> T) {
abstract fun enhance(t: T)
fun cook(): T {
val food = initT()
food.isHeated = true
food.name = "heated $name"
enhance(food)
return food
}
}
class PotatoChipCooker : Cooker<PotatoChip>({ PotatoChip() }) {
override fun enhance(t: PotatoChip) {
t.name = "salted ${t.name}"
}
}
class PancakeCooker : Cooker<Pancake>({ Pancake() }) {
override fun enhance(t: Pancake) {
t.name = "${t.name} coated with maple syrup"
}
}
Note that I removed the optional semicolons and used string templates instead of concatenations. Also, the cook method can be simplified to:
fun cook() = initT().apply {
isHeated = true
name = "heated $name"
enhance(this)
}