Nested forEach Loops Scope Issue

I’m trying to implement a rule that uses nested forEach loop, like this:

    //for every member 't' of autoT, search through all members 'x' of lightsAux
    //and find the matching autoMode item 'a'. If a is ON and the color temp has changed, send the command    
    autoT.members.forEach[t |
        lightsAux.members.filter[x | x.name == t.name.split("_")[0]+"_autoMode"].forEach[a |
            if (a.state == ON && t.state != currentColorTemp) {
                t.sendCommand(currentColorTemp)
            }
        ]
    ]

However, when the rule executes, I get the following error in the log:

Could not invoke method: org.apache.commons.lang.StringUtils.split(java.lang.String,java.lang.String,int) on instance: null

I suspect the problem is that in the inner filter(), the variable t from the outer forEach() is out of scope and thus null, but I’m not sure why or how I can overcome it. Does anyone have any suggestions?

Personally I would not use this big one-liner, because the same split would have to be done for each x again and again. I’d perform the split once and use the resulting string in the following for each. That way you could use a logInfo in order to check on which autoT member the split produces an error.

@opus, Thanks for the reply. I hadn’t thought of the split being done multiple times. I re-wrote the code to eliminate that problem, and I added some logInfo lines to try to see what’s going on. Here’s what I have now:

    autoT.members.forEach[t |
        val tName = t.name
        logInfo("color temp item name",tName)
        val aName = tName.split("_")[0] + "_autoMode"
        logInfo("auto mode item name",aName)
        var boolean autoON = (lightsAux.members.filter[x | x.name == amName].iterator().next().state == ON)
        if (autoON == true && t.state != currentColorTemp) {
            t.sendCommand(currentColorTemp)
        }
    ]

And here’s what I get in the log:

2018-03-31 19:28:47.766 [INFO ] [me.model.script.color temp item name] - cornerLamp_T
2018-03-31 19:28:47.771 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Auto Color Temperature Set': Could not invoke method: org.apache.commons.lang.StringUtils.split(java.lang.String,java.lang.String,int) on instance: null

So for some reason, split("_") is failing on the string “cornerLamp_T”, which doesn’t make any sense to me.

Since you can’t call array members like

MyArray[0]

and have to use

MyArray.get(0)

instead, you migth want to split that line as well.

 val aNameArray = tName.split("_")
val aName= aNameArray.get(0)+ "_autoMode"
1 Like

That was my problem. I substituted .get(0) for [0] and now everything is working as expected. Thanks!

I’m glad you got it working but there are features built into the Rule’s DSL that makes this approach a little awkward.

val boolean autoON = (lightsAux.members.findFirst[ x | x.name == amName ] as GenericItem).state == ON

// or
val boolean autoON = (lightsAux.members.filter[ x | x.name == amName ].head as GenericItem).state == ON

// or to save the exra boolean operation
val autoON = lightsAux.members.findFirst[ x | x.name == amName ] as GenericItem
if(autoON.state == ON && t.state != currentColorTemp) {
1 Like

I’m not sure I understand exactly what the ramifications of the way I showed it are, but either way, I like your suggestions better than what I had as they’re a little easier to read and understand. I ended up using the third one. Thanks!

The ramifications are really not all that much. Your one-liner is quite a bit longer and you have more method calls and it is a little harder to read. Overall it would perform a little less well but you are unlikely to ever notice the difference even if you had thousands of Items in the Groups. The biggest thing is it is harder to read.

Look at the Design Pattern: Working with Groups in Rules for additional tricks you can do with Lists.

Ah. Ok. Makes sense. Reading that very thread is actually one of the things that initially prompted me to start refactoring my rules to be group-based. I really like that approach. I’m pretty new to Java, so most of my problems are either syntax-related or just not yet knowing what methods and data structures are available (hence the initially clunky code). That thread, as well as some of your others, has been very helpful on that front, though. Thanks for the additional clarification.

Just to be clear, the Rules DSL is not Java. It runs on the Java Virtual Machine and has access to Java classes but it is a wholly different language and almost everything discussed on that DP and above have nothing to do with Java.

One way to discover what methods are available is to use VSCode with the openHAB extension. Start typing and it will pop up a list of all the valid ways to finish what you are typing. It is a handy way to find all of the avalable methods on a object.

1 Like