[SOLVED] Working with groups and Stringbuilder

I am quite fascinated of building Strings after filtering the group members. I studied the Thread “Working with groups in rules”, but I would like to access filtered items. Actually I am building a string of the name of opened windows and doors and pass it to the tts to the Amazon Echo.
I would like to jump not the last filtered item, but to the item before the last one.
Exampe: My built string is" Right now the window 1, the window 2, the window 3, is opened"
-> the better version would be :“Right now the window 1, the window 2 and the window 3 is opened” .
So I have to read the number of filtered members with.size and jump to the last item -1 to add the “and” and delete the comma inside my string.
Maybe someone (@rlkoshak :relaxed:) could give me a hint for doing that.
A small piece of my code for more than one opened door:

if (AlleTueren.members.filter[contact | contact.state != NULL].filter[contact | contact.state == OPEN].size >1){
Ansage.append("Es ist noch die ")
//Counter = AlleTueren.members.filter[contact | contact.state != NULL].filter[contact | contact.state == OPEN].size
string = AlleTueren.members.filter[ i | i.state == OPEN ].map[ name ].reduce[ s, name | s + " und die " + name ]
Ansage.append(" geöffnet")

This is really complex because there are so many edge cases (e.g. what if there is only one door open? I’ve therefore never tried to solve it. It’s never been a big enough bending to justify the added complexity. My messages like this go:

The following sensors are known to be offline: sensor 1, sensor2, sensor3

Look at the map reduce and forEach examples on the Xtend site. There may be a way to get the index of the element and you can test those indeed against the size of the list.

Failing that you will have to do a traditional for loop over three last if filtered items.

Therefore I use three main cases:

  1. All door closed-> .size ==0
  2. Just one door opened -> .size ==1
  3. More than one (the actual topic of this thread) -> .size >1

I was thinking of manipulating the string with string.replaceFirst(",$", “und”) , but obviously I couldn’t find it in the Stringbuilder.

Now that I’m sitting at a computer I found an example in the Xtend docs that may be useful.

val StringBuilder message = new StringBuilder
val opened = Doors.members.filter[door | door.state == OPEN] // no need to filter the NULL out separately

message.append("Right now ")

// No doors
if(opened.size == 0) message.append("there are no doors open")

// One door
else if(opened.size == 1) message.append(" and " + opened.get(0).name + " is open")

// Two or more doors
else {
    message.append("and "
    opened.forEach[door, index | 
        if(index == 0) message.append(door.name) // first element
        else if(index == opened.size-1) message.append(" and " + door.name + ".") // last element
        else message.append(", " + door.name) // middle elements

You will probably benefit from Design Pattern: Human Readable Names in Messages as well to get more friendly message from the Item names.

1 Like

Like always a very nice solution. I will give it a try and to be honest I was already playing with the index in forEach, but couldn’t get it working. Thanks very much :grinning:

Rich, i would like to use size to rule like this, but get error

val doorsSensorsCount = gDoorsSensors.members.filter[ i | i.state == OPEN].forEach[i |  //select all the doors that have DoorSensor == OPEN
    logInfo("TEST", "name " + doorsSensorsCount.size)

i have treid all the ways, but i get always

2019-12-06 21:27:41.658 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Opened Doors Notification': The name 'doorsSensorsCount' cannot be resolved to an item or type; line 62, column 31, length 17

Maybe it is deprecated?

Simplified version is also with error

  val doorsSensorsCount = gDoorsSensors.members.filter[ i | i.state == OPEN]
    logInfo("TEST", "name " + doorsSensorsCount.size)

I understand that these are stupid questions from me. But if i have question about php i ask google “php array size”.
I do not want to bother you and other with them. Please aim me on a manual where i can find all of this by myself.

Thanks one more time!

That’s because the code makes no sense. Let’s break it down.

  • val doorsSensorsCount: create a value named doorsSensorsCount
  • =: Set the result of the everything to the right to the value; NOTE doorSensorsCount will not actually exist until everything after the = is done executing.
  • gDoorSensors.members: get the List of all the members of the Group gDoorSensors
  • .filter[ i | i.state == OPEN]: reduce the List of all members to a list of only those whose state is OPEN
  • .forEach[ i |: execute the following code for each member of the filtered list of Items (i.e. for each Item whose state is OPEN)
  • logInfo("TEST", "name " + doorSensorsCount.size): This makes no sense. For one doorSensorsCount doesn’t even exist yet (see above). For another, why would you want to log out this log statement for each door that is OPEN? That’s what you are telling it to do.

Put in English you are telling the Rules to “For each member of gDoorSensors that is OPEN log out doorSensorsCount.size and store the result into doorSensorsCount.” That makes no sense, you can’t log out something that hasn’t been created yet, you can’t store the result of a forEach into a variable because forEach doesn’t return anything to store in a variable, and you don’t want to do this 5 times if you happen to have five doors open.

If you want to get the number of OPEN doors:

val doorSensorsCount = gDoorsSensors.members.filter[ i | i.state == OPEN].size
1 Like

Thanks a lot, Rich for description and links!!!
Some of them i use already, and some will read carefully!

Rich, hello.

We have found a stange behavior of String Builder with Vincent https://community.openhab.org/t/solved-one-line-in-rule-makes-system-die/87134
There is two code line

This one works well

val message = "opened time" + ((now.millis - new DateTime(doorLastChange.state.toString).millis)/1000).toString)

And this one makes system freez (but also works).

message.append("opened time" + (((now.millis - new DateTime(doorLastChange.state.toString).millis)/1000).toString) + "")

We can not understand why.
Maybe you know the reason?


StringBuilder is one of the core classes of Java. It’s unlikely that anything had changed with StringBuilder in forever I’ve no idea what could be causing problems with that line but it is unlikely the StringBuilder itself that is the cause. That’s about all I can offer.

1 Like

This can’t work well, since it has a spare closing paren. Works fine after removing it.

This one is badly written, but worked fine. What error are you getting? Is doorLastChange.state NULL?

You are not using your StringBuilder very well. And why the empty String at the end?

    val message = new StringBuilder("opened time ").append((now.millis - new DateTime(Virtual_DateTime_1.state.toString).millis)/1000)

As i mentioned, i get NO error, but the system freezes after i use this line.
I use 2.5 M4.