Comparison of two states in corda - kotlin

I have created two states in corda. Now I want to compare the fields of these two states and create a third state based on the comparison. Is it possible? If possible how to do it? Is there any ideal programming solution other than comparing each field?

If you are using Kotlin, you can use data classes: https://kotlinlang.org/docs/reference/data-classes.html For example, the following will return true:
data class Person(val name: String)
val person1 = Person("John")
val person2 = Person("John")
person1 == person2
Because equality is determined solely based on the fields in the primary constructor. Alternatively, you can override the equals method of your class.
If you are only trying to compare some of the fields, you can compare them directly in a large if statement. But if you are using a data class, you can also compare them by creating a copy where the fields you don't want to compare have been set to be equal in both instances. For example, the following will return true:
data class Person(val name: String, val age: Int, val address: String)
val person1 = Person("John", 24, "London")
val person2 = Person("John", 25, "London")
person1 == person2.copy(age = person1.age)

Related

Values loss for copy() for Kotlin data class

I have such data class:
data class BookObject(
val description: String?,
val owningCompanyData: OwningCompanyData?,
) {
var id: String? = null
var createdAt: Instant? = null
var createdBy: String? = null
var modifiedAt: Instant? = null
fun update(command: CreateOrUpdateBookObjectCommand): BookObject =
this.copy(
description = command.description,
owningCompanyData = command.owningCompanyData
)
}
When I use the update function for an object with completely filled fields, I get an object with empty id, createdAt, createdBy, modifiedAt fields (they become equal to null). But why is this happening? Why do these fields lose their values?
The kotlin documentation says:
Use the copy() function to copy an object, allowing you to alter some
of its properties while keeping the rest unchanged.
The answer actually is present in your link, located in the paragraph just before "Copying".
The compiler only uses the properties defined inside the primary constructor for the automatically generated functions.

Android Room Embedded Field Not Compiling

I am trying to create an embedded field. This is a simple example but I can't get this simple example to work. Eventually I need to have 3 levels of embedded items but trying to get this test case to work.
#Entity(tableName = "userItemsEntity")
#Parcelize
data class Item(
var objecttype: String?,
#PrimaryKey(autoGenerate = false)
var objectid: Int?,
var subtype: String?,
var collid: Int?,
#Embedded
var name: Name?
) : Parcelable
#Parcelize
data class Name(
var primary: Boolean? = true,
var sortindex: Int? = null,
var content: String? = null) : Parcelable
When I try and compile it it complains on the DAO that the updateItem()
SQL error or missing database (no such column: name)
DAO function
#Query("UPDATE userItemsEntity SET " +
"objecttype=:objecttype, objectid=:objectid, subtype=:subtype, collid=:collid, name=:name " +
"WHERE objectid=:objectid")
fun updateItem(
objecttype: String?,
objectid: Int,
subtype: String?,
collid: Int?,
name: Name?)
The reason is as it says there is no name column. Rather the table consists of the columns, as per the member variables of the EMBEDDED class (i.e. primary, sortindex and content).
i.e. the table create SQL is/will be :-
CREATE TABLE IF NOT EXISTS `userItemsEntity` (`objecttype` TEXT, `objectid` INTEGER, `subtype` TEXT, `collid` INTEGER, `primary` INTEGER, `sortindex` INTEGER, `content` TEXT, PRIMARY KEY(`objectid`))
Room knows to build the respective Name object from those columns when extracting rows.
So you could use :-
#Query("UPDATE userItemsEntity SET " +
"objecttype=:objecttype, objectid=:objectid, subtype=:subtype, collid=:collid, `primary`=:primary, sortindex=:sortindex, content=:content " +
"WHERE objectid=:objectid")
fun updateItem(
objecttype: String?,
objectid: Int,
subtype: String?,
collid: Int?,
primary: Boolean?,
sortindex: Int?,
content: String?
)
note that primary is an SQLite token and thus enclosed in grave accents to ensure that it is not treated as a token. Otherwise you would get :-
There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (near "primary": syntax error)
However, as you are using a WHERE clause based upon the primary key (objectid) then the update will only apply to a single row and as such you can simply use:-
#Update
fun update(item: Item): Int
obviously the function's name need not be update it could be any valid name that suits.
this has the advantage of not only being simpler but of returning the number of rows updated (would be 1 if the row exists, otherwise 0)
Impementing a name column
If you want a name column and for that name column to hold a Name object. Then, as SQLite does not have storage/column types for objects then you would not EMBED the Name class.
You would have var name: Name? with an appropriate TypeConverter that would convert the Name object into a type that SQLite caters for :-
TEXT (String),
REAL (Float, Double...),
INTEGER (Long, Int ...) or
BLOB (ByteArray)).
Typically String is used and typically GSON is used to convert from an object to a JOSN String.
SQlite does have a NUMERIC type. However, Room doesn't support it's use. I believe because the other types cover all types of data and NUMERIC is a catch-all/default.
However, using a JSON representation of an object, introduces bloat and reduces the usefulness of the converted data from an SQL aspect.
For example say you had :-
#Entity(tableName = "userOtherItemsEntity")
#Parcelize
data class OtherItem (
var objecttype: String?,
#PrimaryKey(autoGenerate = false)
var objectid: Int?,
var subtype: String?,
var collid: Int?,
var name: OtherName?) : Parcelable
#Parcelize
data class OtherName(
var primary: Boolean? = true,
var sortindex: Int? = null,
var content: String? = null) : Parcelable
Then the underlying table does have the name column. The CREATE SQL, generated by Room, would be :-
CREATE TABLE IF NOT EXISTS `userOtherItemsEntity` (`objecttype` TEXT, `objectid` INTEGER, `subtype` TEXT, `collid` INTEGER, `name` TEXT, PRIMARY KEY(`objectid`))
However, you would need TypeConverters which could be :-
#TypeConverter
fun fromOtherName(othername: OtherName ): String {
return Gson().toJson(othername)
}
#TypeConverter
fun toOtherName(json: String): OtherName {
return Gson().fromJson(json,OtherName::class.java)
}
the first using Gson to convert the object to a JSON string, e.g. when inserting data
the second converts the JSON string to an OtherName object.
using Item with Name embedded then data would be stored along the lines of :-
Whilst with the OtherItem with OtherName being converted then the data (similar data) would be along the lines of :-
in the former the 3 Name columns would take up about (1 + 1 + 12) = 16 bytes.
in the latter, The OtherName columns (discounting the word Other whenever used) would take uo some 55 bytes.
the latter may require more complex and resource expensive searches if the components of the OtherName are to be included in searches.
e.g. #Query("SELECT * FROM userItemsEntity WHERE primary") as opposed to #Query("SELECT * FROM userOtherItemsEntity WHERE instr(name,'primary\":true') > 0")

Kotlin create new object using another object as the default constructor params

data class Person(val age: Int, val name: String)
Is there a simple way to create a new Person object using another object as a template to fill in the required constructor params?
// Example of what I'd like to be able to do
val person1 = Person(age = 35, name = "Cory")
val person2 = Person(person1, name = "Jeff")
You can use copy() method, available in data classes, and override parameters you want to change:
val person1 = Person(age = 35, name = "Cory")
val person2 = person1.copy(name = "Jeff")
https://kotlinlang.org/docs/reference/data-classes.html#copying
Every data class in Kotlin has a copy function:
val person2 = person1.copy(name = "Jeff") // but its age is still 35 (from Cory)
All parameters are optional in the copy function, so you can overwrite / change the ones you like.
Note that the copy() call performs a shallow copy
See: Data Class Copying

Room Relation Specify Column Names When Extending

I am working with some datasets in my room database, and my method is to have one table with information about a dataset called DatasetInfo which stores things like name, type of value stored, id, etc; and a second table where I store the values in 3 columns: (id, date, value). This ordered triplet is defined as a DatasetValue entity. Here, (date, value) is an ordered pair that I want to plot.
To plot these ordered pairs, I have to convert them to a list of Entry objects, where Entry takes the values x and y. It makes the most sense to query my database and simply ask for List<Entry>, because right now I ask for List<DatasetValue> and then I have to map that result to List<Entry> which is unnecessary.
I query for the dataset information table DatasetInfo as follows:
data class DatasetWithValues(
#Embedded
var datasetInfo: DatasetInfo,
#Relation(
parentColumn = DATASET_COLUMN_DATASET_ID,
entityColumn = VALUES_COLUMN_ID,
entity = DatasetValue::class,
)
var values : List<Entry>
)
Now, as I said above, Entry has values x and y, and Dataset calls them date and value. Of course, when I ask for this relation, it will fail because it doesn't know how to assign values from a table with the columns id, date, and value to an object which takes x and y. So, I define a new class:
class DatasetEntry(
#ColumnInfo(name = "date")
var date : Float,
#ColumnInfo(name = "value")
val value : Float
) : Entry(date, value)
and then make the following adjustment:
//var values : List<Entry>
var values : List<DatasetEntry>
That does nothing. The code doesn't compile because:
SQL error or missing database (no such column: x)
Well, what if I instead write:
class DatasetEntry(
#ColumnInfo(name = "date")
var date : Float,
#ColumnInfo(name = "value")
val value : Float
) : Entry(){
init{
x = date
y = value
}
}
That doesn't help either, same error. Even if I remove that init call, it still wants x.
The plot thickens, because inside of Entry I can see x is declared private. So I have absolutely no clue what is happening here. How does Room even know to look for x? Is there any work around for this other than renaming the columns in my table to x and y?
Is there any work around for this other than renaming the columns in my table to x and y?
If you have such option it would be the easiest. Still there are some options you could consider:
1. Mapping Room's result to needed one
So, you ask Room for some raw result and then map it to ready one. For that you add 2 classes:
data class DatasetWithValuesRaw(
#Embedded
var datasetInfo: DatasetInfo,
#Relation(
parentColumn = DATASET_COLUMN_DATASET_ID,
entityColumn = VALUES_COLUMN_ID,
)
var values : List<DatasetValue>
)
data class DatasetWithValuesReady(
var datasetInfo: DatasetInfo,
var values : List<Entry>
)
Let's say you have a dao method:
Query("select * ....")
fun getRawData(): List<DatasetWithValuesRaw>
For mapping you use:
fun getReadyData() =
getRawData().map { item ->
DatasetWithValuesReady(item.datasetInfo,
item.values.map { Entry(x = it.date, y = it.value)
}) }
2. Replacing Room's #Relation with explicit query
It's not what you really want, but still is an option.
Use class like that:
data class DatasetWithSeparateValues(
#Embedded
var datasetInfo: DatasetInfo,
#Embedded
var value : Entry // <----- it's not a list, just a single value
)
and in your dao you set query with explicit columns' names (x and y). Something like that:
Query("SELECT *, values.date as x, values.value as y FROM dataset LEFT JOIN values on dataset.DATASET_COLUMN_DATASET_ID = values.VALUES_COLUMN_ID")
fun getData(): List<DatasetWithSeparateValues>
As a result you'll get a list, but if there is a one dataset with 5 values you'll get inside list 5 items with the same dataset and separate values. After that you could use Kotlin collection's methods (groupBy for example) to prettify result in some way

Kotlin grouping, mapping and so on

I know that a lot of this topics exists but I don't understand this topic at all. And an exact explanation of what is happening for a beginner is needed.
I have a list of persons:
val person1 = Person("A", 8, 24,"darts")
val person2 = Person("A", 8, 24,"football")
val person3 = Person("A", 2, 24,"basketball")
val person4 = Person("B", 8, 24,"skiing")
val person5 = Person("B", 1, 24,"snowboard")
where
data class Person (val street: String, val number: Int, val age: Int, val hobby: String){
override fun toString(): String {
return "$street $number $age $hobby"
}
}
And now to sum up. If I use a groupBy the result will be Map with key, and values Am I right?
Where keys are the atrributes on which I am grouping by like:
var grouping = list.groupBy { it.street }
result is:
{A=[A 8 24 darts, A 8 24 football, A 2 24 basketball], B=[B 8 24 skiing, B 1 24 snowboard]}
And now I would like to group by multiple fields like street and number. How to do this?
And moreover I would like to specify a LIST (I have to do some kind of projection from this map?) of hobbies which people have under adresses and numbers. For example under adress A number 8 i have a list of (darts,football).
#Edit
Do I have somehow to divide this list as 2nd object class?
Yes, groupBy returns a map where the keys are whatever was returned by the lambda and values are lists of items from the original list.
To group by a pair of things, you need to be able to have a key that represents all those things and be unique. The probably means another data class, or maybe you could concatenate the as a String.
data class Address(val street: String, val number: Int)
val grouping = list.groupBy { Address(it.street, it.number) }
And to make the values of the map simply the hobby, you can use mapkeys:
val addressesToHobbies = grouping.mapKeys { it.map(Person::hobby) }