Object copying, Kotlin - kotlin

I'm trying to model car object in Kotlin, and I have a following structure:
data class CarModel(
val brand: Brand, val modelName: String
) {
val versionNumber = VersionNumber(value = 1)
override fun toString(): String {
return "CarModel(brand=$brand, modelName=$modelName, versionNumber=${versionNumber.value})"
}
fun incrementVersionNumber(): CarModel {
val newCarModel = this.copy()
newCarModel.versionNumber = this.versionNumber.value + 1 //ERROR (Val cannot be reassigned)
return newCarModel
}
}
data class VersionNumber(val value: Int) {}
Since version number always has to be 1 initially, the idea is to allow incrementation of the version number only through incrementVersionNumber method. I tried to achieve that with object cloning, meaning return the copy of the object only with version number changed. But for that to work i would need to change the versionNumber property to var but i want to keep it immutable. Is there some way to get this to work while keeping the versionNumber property as val

If you want to completely lock down unexpected behaviour; for example, someone deliberately changing versionNumber to 0, then you would need to use a regular class rather than a data class, as you'll be able to return new instances with any of the properties changed using copy().
Consider this approach, which uses a secondary constructor to prevent erroneous entries on instantiation.
class CarModel private constructor (val brand: Brand, val modelName: String, val versionNumber: VersionNumber) {
constructor(brand: Brand, modelName: String) : this(brand, modelName, VersionNumber())
fun incrementVersionNumber(): CarModel {
return CarModel(brand, modelName, VersionNumber(versionNumber.value + 1))
}
override fun toString(): String {
return "CarModel(brand=$brand, modelName=$modelName, versionNumber=${versionNumber.value})"
}
}
data class VersionNumber(val value: Int = 1)
You could also introduce an init block to check that version is greater than zero, but you won't be able to check that the current version number is an increment of the previous instance:
data class CarModel(val brand: Brand, val modelName: String, val versionNumber: VersionNumber = VersionNumber()) {
init {
require(versionNumber.value > 0) { "Version number must be greater than zero." }
}
fun incrementVersionNumber(): CarModel {
return CarModel(brand, modelName, VersionNumber(versionNumber.value + 1))
}
override fun toString(): String {
return "CarModel(brand=$brand, modelName=$modelName, versionNumber=${versionNumber.value})"
}
}
You could also move the require check to the VersionNumber class:
data class VersionNumber(val value: Int = 1) {
init {
require(value > 0) { "Version number must be greater than zero." }
}
}
Finally, if you really want a class to behave like a data class, then you will need to override the default implementations for equals and hashCode in order to obtain proper value semantics:
class CarModel(val brand: Brand, val modelName: String, val versionNumber: VersionNumber = VersionNumber()) {
fun incrementVersionNumber(): CarModel {
return CarModel(brand, modelName, VersionNumber(versionNumber.value + 1))
}
override fun equals(other: Any?): Boolean {
return this === other
|| other is CarModel
&& other.brand == brand
&& other.modelName == modelName
&& other.versionNumber == versionNumber
}
override fun hashCode(): Int {
return Objects.hash(brand, modelName, versionNumber)
}
override fun toString(): String {
return "CarModel(brand=$brand, modelName=$modelName, versionNumber=${versionNumber.value})"
}
}

You could do what you ask with a private constructor (including the version number), a factory method to create new instances and the copy method to create instances with versions != 1.
data class CarModel private constructor(
val brand: Brand, val modelName: String, val versionNumber: VersionNumber
) {
companion object {
fun create(brand: Brand, modelName: String): CarModel {
return CarModel(brand, modelName, VersionNumber(1))
}
}
fun incrementVersionNumber(): CarModel =
this.copy(versionNumber = VersionNumber(versionNumber.value + 1))
}
Also you are probably easier off using an inline class for VersionNumber instead.

You can use a private setter in your CarModel class:
var versionNumber: VersionNumber = VersionNumber(value = 1)
private set
fun incrementVersionNumber(): CarModel = copy().apply {
versionNumber = VersionNumber(versionNumber.value + 1)
}

Related

Kotlin annotation changing of target

I have a annotation AggregateId that could be set on method params and properties and that I will use to retrieve some id :
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
#Retention(AnnotationRetention.RUNTIME)
annotation class AggregateId
I wrote that test case :
data class Example(
#AggregateId
val id: UUID
)
class AggregateIdTests {
private val exUuid = UUID.fromString("bae30706-f949-4eb5-b091-d51a13ddc832")
#Test
fun test() {
val ex = Example(id = exUuid)
val id = resolve(ex)
Assertions.assertThat(id).isEqualTo(exUuid)
}
private fun resolve(target: Any): UUID? {
val prop = target::class.declaredMemberProperties.find {
it.findAnnotation<AggregateId>() != null
}
return prop?.getter?.call(target) as UUID?
}
}
That actually works.
But if I add this class in the code :
class TestClass {
fun aMethod(#AggregateId param: UUID) {
}
}
Suddently the AggregateId changes of target for the other class. Even though I didn't change the rest of the code. What is the explaination of this ?
(using kotlin 1.5)

Getting list of enums from bitmask in Kotlin

I have an enum class SettingsVisibility inside an database entity, which I am converting to a bitmask to store in the database. I am trying to reverse the conversion to bitmask, and get a list of enums as a return value. So if I have an enum with values ONE(1), TWO(2), FOUR(4), then it'll store as Enum(7). I want to take 7 and convert it to {ONE, TWO, FOUR}.
My code is below. I have the SettingsVisibility enum with integer values which are stored in the DB. When I try to retrieve from the database, Objectbox will use the given PropertyConvertor to marshall/unmarshall the data. When I want to convertToEntityProperty, it should return a list of just the saved enums, but at the moment it returns a list of all the enums. I can pass a databaseValue of 12 and it will return all enums instead of just 2 (LOCATION AND PAYMENTS).
I think the issue is the usage of enumClass.enumConstants because it gets all the values, but then the filter doesn't work on this, so I am stuck.
#Entity
data class Settings(
#Id override var id: Long = 0,
#Convert(converter = DocumentVisibilityConverter::class, dbType = Int::class)
val showItems: List<SettingsVisibility>
) : Identifiable<Long> {
lateinit var organisation: ToOne<Organisation>
constructor() : this(
showItems = emptyList(),
)
enum class SettingsVisibility(override val bit: Int) : Flags {
USERS(1),
FINANCE(2),
LOCATION(4),
PAYMENTS(8),
MESSAGES(16),
ERRORS(32),
CANCELLATIONS(64)
}
internal class DocumentVisibilityConverter
: BoxConverters.EnumFlagConverter<SettingsVisibility>(SettingsVisibility::class.java)
}
So for example, if I store the first 3, the database value will be 7 (1+2+4).
The database is ObjectBox and here are the property converters:
abstract class EnumFlagConverter<E>(private val enumClass: Class<E>) : PropertyConverter<List<E>, Int> where E : Enum<E>, E : Flags {
override fun convertToDatabaseValue(entityProperty: List<E>?): Int? {
return entityProperty?.toBitMask()?.value
}
override fun convertToEntityProperty(databaseValue: Int?): List<E>? {
return databaseValue?.let(::BitMask)?.enabledValues(enumClass)
}
}
class BitMask(val value: Int)
interface Flags {
val bit: Int
fun toBitMask() = BitMask(bit)
fun <T> BitMask.enabledValues(enumClass: Class<T>): List<T>? where T : Enum<T>, T : Flags? {
return enumClass.enumConstants?.filter(::hasFlag)
}
infix fun <T : Flags?> BitMask.hasFlag(flag: T): Boolean {
if (value == 0 || (value > 0 && flag?.bit == 0)) {
return false
}
return true
}
Maybe the logic in hasFlag is wrong, because I think that just gets every enum if it isn't 0.
Answer was to replace return true, with:
if (flag?.bit?.toByte() == null) {
return false
}
return (this.value.toByte().and(flag.bit.toByte()) == flag.bit.toByte())
This is basically: bit & mask == bit

How to print multiple attrributes from a hashMap that is a property inside a toString override

I am learning Kotlin and writing code to check my understanding. I'm trying to use a toString override to print the values of a hashMap that is a property of a class. I can't get it to work. Instead I get output like "kotlin.Unit() -> kotlin.Unit". Also, I don't understand why the values of the hashMap ARE printing out before the toString output. I don't know where that output is coming from. Please help me. Thanks. Below is my code and the output I'm getting.
Code:
package ch07.ExpandoObject
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
class Person(
val name: String = "",
age: Int? = null,
var isMarried: Boolean? = null ,_attributes: kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
)
:PropertyChangeAware()
{
var _attributes : kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes!!.set(attrName, value)
_attributes!!.set("name", this.name)
}
override fun toString() = "Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) " +
"${_attributes?.get("name")} " + "$name " +
this._attributes!!.forEach { (attrName, value) -> println("$attrName = $value") } +
{
for ((attrName, value) in this._attributes!!) {
println("attribute $attrName = ${this._attributes!![attrName]}")
}
}
val _age = ObservableProperty(propName = "age", propValue = age, changeSupport = changeSupport)
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(initialValue = age?:99999,onChange = observer)
}
class ObservableProperty(val propName: String,
var propValue: Int?, val changeSupport: PropertyChangeSupport
) {
fun getValue(): Int? = propValue
fun setValue( newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
open class PropertyChangeAware {
val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
fun main(args: Array<String>) {
val p = Person("Bob", 89, isMarried = false)
val data = mapOf("lastname" to "Jones", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p)
}
Here is the current output:
name = Bob
company = JetBrains
lastname = Jones
Person(name="Bob", age=89, isMarried=false) Bob Bob kotlin.Unit() -> kotlin.Unit
Thanks, again, for any help.
You should not use print() or println() functions inside toString() because they output their arguments to the standard output immediately instead of appending them to the string returned to the caller.
Let's examine the output kotlin.Unit() -> kotlin.Unit you're getting. It consists of two parts:
kotlin.Unit is the string representation of attributes!!.forEach { ... } expression. forEach function returns without value, and in Kotlin it's expressed by returning the Unit object value. Its string representation is appended to the string you're returning.
the second part, () -> kotlin.Unit, is also the string representation of the lambda function expression { for((attrName, value) in ...) }. This function takes no parameters, and returns without value, which means that its type is () -> Unit. Note that in Kotlin the block { ... } declares a local lambda function. If you instead want to run the code inside of that block, use the run function: run { ... }
The goal of toString function is to build the string representation of an object. And for that you can use buildString function:
override fun toString() = buildString {
append("Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) ")
append("${_attributes?.get("name")} ").append("$name ")
this._attributes!!.forEach { (attrName, value) -> append("$attrName = $value") }
for ((attrName, value) in this._attributes!!) {
append("attribute $attrName = ${this._attributes!![attrName]}")
}
}
This function creates a StringBuilder and passes it as a receiver to its functional argument, where you call append or appendln on that receiver. Then buildString converts that string builder to a string and returns it.

Idiomatic way to create a variable spring data mongodb Query in Kotlin?

Using Kotlin and spring data mongodb, I'm trying to find the most idiomatic method that can receive nullable parameters (through user defined filters) and create a MongoTemplate Query with the non-null ones (which, in Java, would be a bunch of ifs). This is what I came up with, so far, but I wonder if there's a better way:
// extending Query
open class Filter {
// case insensitive 'like' criteria
fun Query.like(field: String, value: String?) = value?.let {
this.addCriteria(Criteria.where(field).regex(it,"i"))
}
// queries from a date, to a date or between two dates
fun <T: Comparable<T>> Query.between(field: String, from: T?, to: T?) {
if (from != null || to != null) {
val criteria = where(field)
from?.let { criteria.gte(it) }
to?.let { criteria.lte(it) }
this.addCriteria(criteria)
}
}
fun Query.onlyActive(active: Boolean?) = when (active) {
true -> this.addCriteria(Criteria.where("active").`is`(true))
else -> null
}
}
// data class extending Filter()
data class myFilter(val: name: String, val type: String?, val content:String?,
val fromNum: Int?, val toNum: Int?, val fromDate: LocalDateTime?,
val toDate: LocalDateTime?, val active? = false): Filter() {
fun toQuery(): Query {
// name is a mandatory param
val query = Query.query(Criteria.where("name").`is`(name))
with(query) {
like("type", type)
like("content", content)
between("num", fromNum, toNum)
between("date", fromDate, toDate)
onlyActive(active)
}
return query
}
}

Kotlin equals and hash code generator

I am aware that in Kotlin classes will have an equals and hashcode created automatically as follows:
data class CSVColumn(private val index: Int, val value: String) {
}
My question is, is there a way to have the implementation just use one of these properties (such as index) without writing the code yourself. What was otherwise a very succinct class now looks like this:
data class CSVColumn(private val index: Int, val value: String) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (javaClass != other?.javaClass) {
return false
}
other as CSVColumn
if (index != other.index) {
return false
}
return true
}
override fun hashCode(): Int {
return index
}
}
In Java with Lombok, I can do something like:
#Value
#EqualsAndHasCode(of="index")
public class CsvColumn {
private final int index;
private final String value;
}
Would be cool if there were a way to tell Kotlin something similar.
From the Data Classes documentation you get:
Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body
So you have to implement equals() and hashCode() manually or with the help of a Kotlin Compiler Plugin.
You can't do something like this for data classes, they always generate equals and hashCode the same way, there's no way to provide them such hints or options.
However, they only include properties that are in the primary constructor, so you could do this for them to only include index:
data class CSVColumn(private val index: Int, value: String) {
val value: String = value
}
... except you can't have parameters in the primary constructor that aren't properties when you're using data classes.
So you'd have to somehow introduce a secondary constructor that takes two parameters, like this:
class CSVColumn private constructor(private val index: Int) {
var value: String = ""
constructor(index: Int, value: String) : this(index) {
this.value = value
}
}
... but now your value property has to be a var for the secondary constructor to be able to set its value.
All this to say that it's probably not worth trying to work around it. If you need an non-default implementation for equals and hashCode, data classes can't help you, and you'll need to implement and maintain them manually.
Edit: as #tynn pointed out, a private setter could be a solution so that your value isn't mutable from outside the class:
class CSVColumn private constructor(private val index: Int) {
var value: String = ""
private set
constructor(index: Int, value: String) : this(index) {
this.value = value
}
}
I wrote a little utility called "stem", which allows you to select which properties to consider for equality and hashing. The resulting code is as small as it can get with manual equals()/hashCode() implementation:
class CSVColumn(private val index: Int, val value: String) {
private val stem = Stem(this, { index })
override fun equals(other: Any?) = stem.eq(other)
override fun hashCode() = stem.hc()
}
You can see its implementation here.
I guess that we have to write equals()/hashCode() manually for now.
https://discuss.kotlinlang.org/t/automatically-generate-equals-hashcode-methods/3779
It is not supported and is planning to be, IMHO.
I guess that we have to write equals()/hashCode() manually for now. https://discuss.kotlinlang.org/t/automatically-generate-equals-hashcode-methods/3779
It is not supported and is planning to be, IMHO.
Below are some reference which may be helpful.
https://discuss.kotlinlang.org/t/how-does-kotlin-implement-equals-and-hashcode/940
https://kotlinlang.org/docs/reference/data-classes.html
https://medium.com/#appmattus/effective-kotlin-item-11-always-override-hashcode-when-you-override-equals-608a090aeaed
See the following performance optimized way (with the use of value classes and inlining) of implementing a generic equals/hashcode for any Kotlin class:
#file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.beatkit.common
import kotlin.jvm.JvmInline
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class HashCode(val value: Int = 0) {
inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class Equals(val value: Boolean = true) {
inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}
#Suppress("NOTHING_TO_INLINE")
object Objects {
inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value
inline fun hashCode(vararg objects: Any?): Int = hashCode {
var hash = this
objects.forEach {
hash = hash.combine(it)
}
hash
}
inline fun hashCode(vararg hashes: Int): Int = hashCode {
var hash = this
hashes.forEach {
hash = hash.combineHash(it)
}
hash
}
inline fun <T : Any> equals(
lhs: T,
rhs: Any?,
allowSubclasses: Boolean = false,
builder: Equals.(T, T) -> Equals
): Boolean {
if (rhs == null) return false
if (lhs === rhs) return true
if (allowSubclasses) {
if (!lhs::class.isInstance(rhs)) return false
} else {
if (lhs::class != rhs::class) return false
}
#Suppress("unchecked_cast")
return builder(Equals(), lhs, rhs as T).value
}
}
This allows you to write a equals/hashcode implementation as follows:
data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
override fun equals(other: Any?): Boolean {
return Objects.equals(this, other) { lhs, rhs ->
combine(lhs.title, rhs.title)
.combineEquals { lhs.bytes contentEquals rhs.bytes }
}
}
override fun hashCode(): Int {
return Objects.hashCode(title, bytes.contentHashCode())
}
}