Bind ItemViewModel to domain class list - tornadofx

I have a Rulebook that contains Rules:
class Rulebook(val rules:MutableList<Rule>)
I have an ItemViewModel for it, as it's used in a multiply-nested selection UI.
class RulebookModel : ItemViewModel<Rulebook> {
val rulesProperty = bind // ... here's my problem
}
What is the correct binding to be able to initialize a tableview with the property?
A naive bind yields the wrong type:
val rulesProperty = bind(Rulebook::rules)
has type Property<MutableList<Rule>>, which tableview() doesn't take.
From another answer here I got Link
val rulesProperty = bind(Rulebook::rules) as ListProperty<Rule>
This yields the correct type, so we get through compilation, but at runtime I get this:
java.lang.ClassCastException: java.util.ArrayList cannot be cast to javafx.collections.ObservableList
Note: The RulebookModel does start life without an item in it yet. I've seen ArrayLists come from empty list factory calls before. Is that possibly my actual problem?
What is the correct way to perform this binding?

Your model needs to have a SimpleListProperty to bind into an itemViewModel
Here is some sample code for how to write the classes and a table view:
data class rule(val name: String, val def: String)
class RuleBookModel{
val rulesProperty = SimpleListProperty<rule>()
var rules by rulesProperty
}
class RuleBookViewModel: ItemViewModel<RuleBookModel>() {
val rules = bind(ruleBook::rulesProperty)
}
class TestView : View("Test View") {
val myRuleBook: RuleBookViewModel by inject()
init {
// adding a rule so the table doesn't look lonely
myRuleBook.rules.value.add(rule("test", "fuga"))
}
val name = textfield()
val definition = textfield()
override val root = vbox{
hbox {
label("Name")
add(name)
}
hbox {
label("Definition")
add(definition)
}
button("Add a rule").action{
myRuleBook.rules.value.add(rule(name.text, definition.text))
}
tableview(myRuleBook.rules) {
column("name", rule::name)
column("def", rule::def)
}
}
}

Related

Recursively filter and map a list of properties

I'm using Kotlin reflection to check if attributes that have a certain annotation are null.
Given the following example:
data class DataClass(
#SomeRandomAnnotation
val otherAnnotated: String?,
val inner: InnerClass
)
data class AnotherDataClass(
#SomeRandomAnnotation
val annotatedProperty: String?,
val dataClass: DataClass
) {
fun checkCreditAnalysisConstrain() {
print(checkConstrain(this))
}
}
And the function that checks it:
fun checkConstrain(parentClass: Any): List<String> {
val filter = parentClass::class.memberProperties.filter {
if (memberIsDataClass(it)) checkConstrain(getMemberPropertyInstance(parentClass, it))
hasAnnotation(it) && propertyIsNull(it, parentClass)
}
return filter.map { formatResult(parentClass, it) }
}
The idea is that the function is going to iterate through the attributes of my classes checking if they have the annotation and checking if the value is null.
If the property is a data class, the code evaluates the properties of the childs, recursively.
After that, I map the results, transforming the KProperty's into a simple String that is human readable, containing the class name and the attribute name.
The problem is that the above code does not work as expected. The properties returned are only the properties from the first-level class.
If, instead of doing a filter, I just run a forEach and print the result, I get the expected attributes. So I'm pretty sure it's related to the recurring inside a filter.
Do you see any way of doing this in a more functional way? I'm just concerned I won't need a "temp" list and add values to the list and reset it afterwards.
Your function recursively calls itself, but does nothing with the returned list of that recursive call. That's why you only get results for the top-level class.
Also, in my opinion, you shouldn't rely on side effects happening from your filter call. It probably works, but the function's documentation does not provide a guarantee that it will be called exactly once per item in the collection. So there should be a separate for-loop to do the recursive calls, and the result should be added onto existing results.
fun checkConstrain(parent: Any): List<String> {
val memberProperties = parent::class.memberProperties
var result = memberProperties
.filter { hasAnnotation(it) && propertyIsNull(it, parent) }
.map { formatResult(parent, it) }
memberProperties.filter { memberIsDataClass(it) }
.mapNotNull { getMemberPropertyInstance(parent, it) }
.forEach { result += checkConstrain(it) }
return result
}
You didn't provide code for several of the functions you used. This is what I used for them:
val KProperty<*>.returnTypeClass get() = this.returnType.classifier as? KClass<*>
fun <T> memberIsDataClass(member: KProperty<T>) = member.returnTypeClass?.isData == true
fun <T> getMemberPropertyInstance(parent: Any, property: KProperty<T>) = property.getter.call(parent)
fun <T> hasAnnotation(property: KProperty<T>) = property.annotations.firstOrNull { it.annotationClass == SomeRandomAnnotation::class } != null
fun <T> propertyIsNull(property: KProperty<T>, parent: Any) = getMemberPropertyInstance(parent, property) == null
fun formatResult(parent: Any, property: KProperty<*>) = "$parent's property(${property.name}) is annotated with SomeRandomAnnotation and is null."

How can I create Hashmap<String,Int : String,String : String,String> in Kotlin for sorting

I know this character (:) is meaningless in my statement, but I wanted to explain what I want. I want to sort a lot of hashmaps adding Arraylist and using sortedBy but I cant because my values return strings.
Here is my code:
newReference.addValueEventListener(object : ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
chatMessages.clear()
for(ds in p0.child(playerIDmatchWhoIs).children){
var hashMap = ds.getValue() as HashMap<String, String>
var datetime = hashMap.get("datetime").toString()
var usermail = hashMap.get("usermail")
var usermessage = hashMap.get("usermessage")
chatMessages.add("${usermail}: ${usermessage}")
recyclerViewAdapter.notifyDataSetChanged()
}
}
})
(I want to sort this hashMap, it has datetime value but is returning string.)
println(hashMap): I/System.out: {datetime=1574807563747, usermessage=jmjgmhg, usermail=1#gmail.com}
I assume that chatMessages is of type List<String>. This is generally bad because you cannot to anything with strings. I would suggest you to create a data class which contains all information about a chat message, like so:
data class ChatMessage(val dateTime: Int, val userMail: String?, val userMessage: String?) : Comparable<ChatMessage> {
override fun compareTo(other: ChatMessage) = this.dateTime.compareTo(other.dateTime)
}
As you can see, this class implements the Comparable<ChatMessage> interface. If you then define the chatMessages list like so
private val chatMessages = mutableListOf<ChatMessage>()
you can call chatMessages.sort() which will then sort the list according to dateTime (see the implementation of compareTo in ChatMessage). The final code would look like that:
data class ChatMessage(val dateTime:Int?, val userMail: String?, val userMessage: String?) : Comparable<ChatMessage> {
override fun compareTo(other: ChatMessage) = this.dateTime.compareTo(other.dateTime)
}
private val chatMessages = mutableListOf<ChatMessage>()
fun yourCode() {
newReference.addValueEventListener(object : ValueEventListener {
/* Use proper variable naming. Nobody will understand, what p0 is, but if you name
it dataSnapshot, everyone knows at a glance. */
override fun onDataChange(dataSnapshot: DataSnapshot) {
chatMessages.clear()
// Again, what is ds exactly? Name it properly.
for (ds in dataSnapshot.child(playerIDmatchWhoIs).children) {
// Kotlin recommends to use val instead of var.
// This way, you know that your variables cannot be modified unless you want them to be modified.
val hashMap = ds.getValue() as HashMap<String, String>
// use indexing instead of the get() method
val dateTime = hashMap["datetime"]
val userMail = hashMap["usermail"]
val userMessage = hashMap["usermessage"]
// TODO: Handle null values properly
chatMessages.add(ChatMessage(dateTime!!.toInt(), userMail, userMessage))
recyclerViewAdapter.notifyDataSetChanged()
}
chatMessages.sort()
}
})
}
This assumes that you want to store your timestamp as an integer. However, I would rather recommend to use a time library like java.time (built into java). In that case, you can use java.time.Instant which has many more possibilities to handle time and all the difficulties to handle time.
Read more about java.time.Instant in the Android docs. If you want to learn how to parse a String to java.time.Instant, this might be interesting.

Returning object of type specified in method arguments instead of AnyRef

I have the following method:
#org.springframework.stereotype.Service
class EntityCacheManager {
def get(cacheId: String, entityClass: Class[_]): AnyRef = { ... }
//...
}
So to use it, i have to write this:
val cachedEntity = entityCacheManager.get(cacheId, classOf[SomeEntity]).asInstanceOf[SomeEntity]
Is there some way to make EntityCacheManager.get() returning instance of type entityClass which is specified in method params? I'd like to avoid casting asInstanceOf every time i use this method. I know it would be nice to use generic definition of type EntityCacheManager, but it's also a spring-managed bean, so i think using generics will cause troubles.
You can use a more idiomatic scala approach by using the ClassTag typeclass
class EntityCacheManager {
def get[T: ClassTag](cacheId: String): T = {
val entityClass = implicitly[ClassTag[T]].runtimeClass
val myObject: T = ??? // you retrieve your object somehow using entityClass
myObject
}
}
you can now use it like this:
val myEntityClassInstance = get[MyEntityClass]("key")

Grails validate fields with default values

We have a class like this in a Grails 2.4.3 application (migrated from 2.3.8):
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
This class is being instantiated in a complex configuration mechanism using a Map like this:
[
noDefault: 0,
withDefault: 2
]
(In fact the Map is part of a huge one, but the class constructor sees this small one.) Formerly the class worked if we omitted the withDefault entry from the config map, using the default value which is not null. In Grails 2.4.3, however, it tells me that this field cannot be null. I can fix it by letting it be null in the constraint, but it lets setting the explicite value null (and overwrite the default value), which causes problem during operation.
Do you know some workaround, which preserves the semantics and correct operation?
Thanx in advance, best regards: Balázs
What you are describing is not consistent with what I would expect and not consistent with the behavior I am seeing. The project at https://github.com/jeffbrown/validatedefaults contains the following code.
At https://github.com/jeffbrown/validatedefaults/blob/master/src/groovy/demo/Foo.groovy
// src/groovy/demo/Foo.groovy
package demo
import grails.validation.Validateable
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
The test at https://github.com/jeffbrown/validatedefaults/blob/master/test/unit/demo/FooSpec.groovy passes:
// test/unit/demo/FooSpec.groovy
package demo
import spock.lang.Specification
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
#TestMixin(GrailsUnitTestMixin)
class FooSpec extends Specification {
void 'test validating default values'() {
given:
def map = [noDefault: 0]
def foo = new Foo(map)
expect:
foo.validate()
}
}
When I run the app I get the same behavior.
// grails-app/conf/BootStrap.groovy
import demo.Foo
class BootStrap {
def init = { servletContext ->
def map = [noDefault: 0]
def foo = new Foo(map)
// this prints true...
println "Foo is valid? : ${foo.validate()}"
}
def destroy = {
}
}
I hope that helps.

Grails - Field error disappears from parent class

I have the following problem:
I have a domain class called customer, with a field discount embedded.
class Customer {
...
String username
Discount discount
static constraints = { ... }
}
class Discount {
Integer item1
Integer item2
static constraints = { item1 min:1, max:100, nullable:true }
}
I have a controller, where a customer's data can be modified. The code goes something like this:
def edit() {
Customer c = Customer.findByUsername(params.userName)
if(request.method != 'GET'){
bindData(c, params)
if(c.validate()) {
//save the result
}
}
println c.dump()//1
model:[customer:c]
}
Then in the edit.gsp I put the following code:
${customer.dump()}//2
${customer.discount.dump()}
Now my problem is, if I have a validation error, for example the user enters 123 for item1, I get the appropriate errors object which says that Customer bean has 1 field error on field discount.item1 when I call println c.dump()//1
In the edit.gsp on the other hand, the customer bean doesn't have any field errors but customer.discount has the mentioned error. This is a big inconvenience, because I want to render errors next to the fields like so:
<g:renderErrors bean="${customer}" field="discount.item1"/>
But the customer bean doesn't have any errors, just the discount bean (therefore I don't get any errors rendered).
Has this problem occured to any of you ?
It seems that #Validatable classes use functionality of spring framework's AbstractBindingResult, which doesn't support this usage (as far as I can tell).
However I was able to create a workaround, which could be used as a taglib to achieve the same effect:
Given two validateable classes
#Validateable
class TestA {
TestB b
static constraints = {
b validator: {
it.validate()
}
}
}
#Validateable
class TestB {
int c
static constraints = {
c min: 100
}
}
One can simply resolve the bean and the attribute and delegate the rendering to the existing taglib.
import test.*
import org.codehaus.groovy.grails.plugins.web.taglib.*
ValidationTagLib validationTagLib = ctx.getBean(ValidationTagLib)
renderErrorsWithNestedFields = { attrs, body ->
def modifiedAttrs = attrs
String[] path = attrs?.field?.split('\\.')
if(path?.size() > 1) {
Object resolvedBean
resolvedBean = attrs.bean
path[0..-2].each {
//Some error handling would not hurt, but this is just a proof of concept
resolvedBean = resolvedBean[it]
}
modifiedAttrs = new HashMap(attrs)
modifiedAttrs.bean = resolvedBean
modifiedAttrs.field = path.last()
}
validationTagLib.renderErrors (modifiedAttrs, null)
}
TestA a = new TestA(b:new TestB(c:3))
a.validate()
renderErrorsWithNestedFields([bean:a, field:'b.c'], null)
And this should render an error for violating the min constraint on c.

Resources