Kotlin: How to use custom setters in primary constructor - kotlin

I don't know how to make it so that when creating an object the values of the parameters "pass through the setters" the closest I've gotten is to duplicate the code, use once in the creation of the object and once again in the setter
class User(var name: String, password: String, age: Int) {
// IDK how use a custom setter in the primary constructor
var password: String = if (password.length > 6) password else throw IllegalArgumentException("Password is too short")
set(value) {
if(value.length > 6)
field = value
else
throw IllegalArgumentException("Password is too short")
}
var age: Int = if (age > 18) age else throw IllegalArgumentException("Age must be 18+")
set(value) {
if(value > 18 )
field = value
else
throw IllegalArgumentException("Age must be 18+")
}
override fun toString(): String {
return "login: $name, password: $password, age: $age"
}
}
fun main() {
val user1 = User("User1", "swordfish", 20)
println(user1)
try{
// This code throw a exception
user1.password = "fish"
}
catch (e: IllegalArgumentException){
println(e.message)
}
// Here we can see if password has changed
println(user1)
try{
val user2 = User("U2", "swordfish", 2)
println(user2)
}
catch (e: IllegalArgumentException){
println(e.message)
}
}
What I want is to be able to check (through a setter) the parameters that are passed in the creation of an object

There may be many approaches to this but you're not wrong.
What I'd do to achieve what you're trying to do is use the builtin error() function that throws an IllegalStateException on the JVM.
class User(var name: String, password: String, age: Int) {
var password: String = password
set(value) {
assertValidPassword(value)
field = value
}
var age: Int = age
set(value) {
assertValidAge(age)
field = value
}
init {
this.password = password
this.age =age
}
override fun toString(): String {
return "login: $name, password: $password, age: $age"
}
private fun assertValidAge(age: Int) {
if (age <=18 ) error("Age must be 18+")
}
private fun assertValidPassword(password: String) {
if(password.length <= 6) error("Password is too short")
}
}
What I've done here:
I've put the validation login inside a function that I can reuse both in the setter and the constructor body
I've added the primary constructor body by specifying the init {} block

Related

How to leave json object as raw string in Moshi

I want be able to save json object as raw json String. I've already have solution wit JSONObject, but it seems a bit redundant. Is there any way to get json string from reader?
class ObjectAsStringFactory : JsonAdapter.Factory {
override fun create(
type: Type,
annotations: MutableSet<out Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
val delegateAnnotations = Types.nextAnnotations(annotations, ObjectAsString::class.java)
?: return null
if (Types.getRawType(type) !== String::class.java) {
throw IllegalArgumentException("#ObjectAsString requires the type to be String. Found this type: $type")
}
val delegateAdapter: JsonAdapter<String?> = moshi.adapter(type, delegateAnnotations)
return ObjectAsStringAdapter(delegateAdapter)
}
class ObjectAsStringAdapter(private val wrapped: JsonAdapter<String?>) : JsonAdapter<String>() {
override fun fromJson(reader: JsonReader): String? {
return (reader.readJsonValue() as? Map<*, *>)?.let { data ->
try {
JSONObject(data).toString()
} catch (e: JSONException) {
return null
}
}
}
override fun toJson(writer: JsonWriter, value: String?) {
wrapped.toJson(writer, value)
}
}
}
Try JsonReader.nextSource(). You can read the encoded JSON bytes either as a String or as a ByteString.
String encodedJson;
try (BufferedSource bufferedSource = reader.nextSource()) {
encodedJson = bufferedSource.readUtf8();
}

Ktor Serializer for class 'Response' is not found

Plugin and dependeny:
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
Application file:
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(ContentNegotiation) {
json()
}
userRouter()
}.start(wait = true)
}
UserRouter:
fun Application.userRouter() {
routing {
get("/users/{id}") {
val id = call.parameters["id"]?.toInt() ?: -1
val user = User("Sam", "sam#gmail.com", "abc123")
val response = if (id == 1) {
Response("hahaha", false)
} else {
Response(user, true) //< - here, use String type will work
}
call.respond(response)
}
}
}
User:
#Serializable
data class User(
val name: String,
val email: String,
val password: String
)
Response:
#Serializable
data class Response<T>(
val data: T,
val success: Boolean
)
Logs:
2021-12-02 18:04:34.214 [eventLoopGroupProxy-4-1] ERROR ktor.application - Unhandled: GET - /users/7
kotlinx.serialization.SerializationException: Serializer for class 'Response' is not found.
Mark the class as #Serializable or provide the serializer explicitly.
at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:91)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:155)
Any help would be appreciated.
The problem is that type for the response variable is Response<out Any> (the lowest common denominator between String and User types is Any) and the serialization framework cannot serialize the Any type.
That's werid, the following works, but to me they are just the same:
get("/users/{id}") {
val id = call.parameters["id"]?.toInt() ?: -1
val user = User("Sam", "sam#gmail.com", "abc123")
/* val response = if (id == 1) { //<- determine response here won't work, why?
Response("hahaha", false)
} else {
Response(user, true)
}
call.respond(response)*/
if (id == 1) {
call.respond(Response("hahaha", false))
} else {
call.respond(Response(user, true))
}
}
You should give a type to a generic when you set a nullable generic to null, otherwise, the plugin doesn't know how to serialize it.
for eg.
//define
#Serializable
data class Response<T>(
val data: T?,
val success: Boolean
)
///usage:
val response = Response<String>(null, false)

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"

Kotlin poet filet not getting generated

I tried to create a class with annotation processor and Kotlin Poet. This is my code:
#AutoService(Processor::class)
class TailProcessor : AbstractProcessor() {
override fun process(elementTypeSet: MutableSet<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean {
roundEnvironment?.getElementsAnnotatedWith(Tail::class.java)?.forEach {
if (it.javaClass.kotlin.isData) {
print("You are doing it right")
val className = it.simpleName.toString()
val pack = processingEnv.elementUtils.getPackageOf(it).toString()
val variables = ElementFilter.fieldsIn(elementTypeSet)
startClassGeneration(className, pack, variables)
} else {
return false
}
}
return false
}
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf(Tail::class.java.name)
private fun startClassGeneration(
className: String,
pack: String,
variables: MutableSet<VariableElement>
) {
val fileName = "Tail$className"
val stringToBePrinted = generateStringFromIncommingValues(variables)
val printFunction = FunSpec.builder("print").addCode("print($stringToBePrinted)").build()
val generatedClass = TypeSpec.objectBuilder(fileName).addFunction(printFunction).build()
val file = FileSpec.builder(pack, fileName).addType(generatedClass).build()
val kaptKotlinGeneratedDir = processingEnv.options[KOTLIN_DIRECTORY_NAME]
file.writeTo(File(kaptKotlinGeneratedDir, "$fileName.kt"))
}
private fun generateStringFromIncommingValues(variables: MutableSet<VariableElement>): Any {
val stringBuilder = StringBuilder()
variables.forEach {
if (it.constantValue == null) {
stringBuilder.append("null\n ")
} else {
stringBuilder.append("${it.constantValue}\n")
}
}
return stringBuilder.toString()
}
companion object {
const val KOTLIN_DIRECTORY_NAME = "sxhardha.tail"
}
}
The problem, directory and file not generating. I tried to rebuild, invalidate cache + restart, clean but none of them works. The build goes successful without any errors but I see no changes. Can you check what is wrong?
I actually found the issue. I wasn't checking right if that class is a data class or not and the condition was never met.
Instead of:
it.javaClass.kotlin.isData
Should have been:
it.kotlinMetadata as KotlinClassMetadata).data.classProto.isDataClass
But it can only be achieved by using this library here.

Get value from annotation failed

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)
}
}