[solved] Using arrays - syntax help

Hello,
I’m trying to use arrays in my rule file.

Here’s what my example .item file:

Number index_val "Index value [%.1f]"
Number SomeValue "Some Random value [%.1f]"

Here’s what I’m trying to do in my .rules file:

import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.util.List
import java.util.ArrayList

var Number temp
val int[] theArray = newArrayList(1, 2, 3, 4, 5, 6, 7, 8)

temp = theArray.get(2)            //this works
temp = theArray.get(index_val.state)    //this does not work

//would like to be able to do this
//theArray[3] = 20
//theArray[index_val.state] = SomeValue.state
theArray.put("3", 20)    //which parameter is index and which is value??  also, bad syntax
theArray.put(index_val.state, SomeValue.state)  //???bad syntax, I don't know what I'm doing.

I’m basically trying to figure out how to retrieve values from the array and do math on it, and then save values back to the array using variables as index positions.

I’m also worried about index values being “numbers”, rather than strictly integers. Not sure if I have to type cast?

Thanks!

A Number item holds a DecimalType, which is the state. The DecimalType can provide various numeric types (intValue, longValue, floatValue, doubleValue). Maybe SomeValue.state.intValue would work to index the array.

First things first, an ArrayList is not a traditional array like you are used to in other programming languages. As far as I can tell you can’t do just plain old arrays in the Xbase based domain specific language of openHAB’s rules. I could be wrong on this point but when I’ve tried in the past doing things the Xtend documentation says I should be able to do I’ve failed to get it to work.

An ArrayList is, as you might notice from the imports, a Java container class and if you ever have questions about it see the ArrayList JavaDocs.

So, from an array list perspective there are several problems with the code above, almost all of which are problems with casting.

The declaration:

val List<Integer> theArray = newArrayList(1, 2, 3, 4, 5, 6, 7, 8)

This helps give the rules engine some hints as to what is going into the array list. Otherwise the members are treated as just Objects or Numbers if you are lucky. It may be redundant but when having problems like this I find it helps to be as specific as possible.

temp = theArray.get(2) 

This works because the get() method on ArrayList takes a primitive int as its argument.

temp = theArray.get(index_val.state)

This doesn’t work because index_val.state returns an object of type State, or if the rules engine is being particularly smart here you are getting a Number. But no matter how smart the rules engine is about figuring out how to cast things, it can’t cast from an Object to a primitive. So you need to take this last little step yourself.

temp = theArray.get((index_val.state as Number).intValue)

The “as Number” part is making double sure that the state is cast to a Number object instead of just being a State object.

// theArray[3] = 20
theArray.put("3", 20)

ArrayList doesn’t have a put() method and you can’t use a String as an index. It looks like you are getting the syntax for a HashMap mixed up with the syntax for ArrayList. What you really want is add():

theArray.add(3, 20)

The first argument must be a primitive integer and the second must be the same type as what your ArrayList is storing (in this case a Number). One thing that may be confusing is that the rules engine is smart enough here to realize that it needs to create an Integer object to represent and store the 20 inside the ArrayList.

So, based on the above the last line should be:

theArray.add((index_val.state as Number).intValue, SomeValue.state as Integer)

For writing this sort of rule and figuring out these sorts of errors I can not recommend highly enough the use of Designer and the <ctrl><space> key combo. Designer will tell you that your code is not syntactically correct as you type it and the key combo will auto-complete what you are typing by giving you a list of possible valid completions. For example if you typed theArray.<ctrl><space> a menu will pop up showing you all of the methods that you can call on theArray with the types each takes as its arguments and what the method returns. You can learn A LOT this way and save a lot of internet searching.

3 Likes

@rlkoshak thanks so much!

It’s still complaining about the assignment statement:

theArray.add((index_val.state as Number).intValue, SomeValue.state as Integer)

I’ve tried:

theArray.add((index_val.state as Number).intValue, (SomeValue.state as Number).intValue)
theArray.add(index_val.state as Integer, SomeValue.state as Integer)

Both ways got complaints.

What’s the specific complaint?

It could be a number of things and I don’t have direct access to Designer right now.

Maybe try this…

theArray.add(index_val.state.intValue, SomeValue.state.intValue)

I’ve verified that this works:

theArray.add(2,20)

However, doing this does not work in Designer, give me the following error on Designer

theArray.add((index_val.state as Number).intValue, (SomeValue.state as Number).intValue)
> type mismatch:  cannot convert from org.openhab.types.State to java.lang.Number

And this gave me this Designer error

theArray.add(index_val.state.intValue, SomeValue.state.intValue)
> cannot resolve element JvmIdentifiableElement intValue

If I include org.openhab.core.library.types.DecimalType in the imports, I can do “as DecimalType” instead of “as Number”.

theArray.add((index_val.state as DecimalType).intValue, (SomeValue.state as DecimalType).intValue)

and this passes the error checking in Designer, but when I run it I get long errors on OpenHAB

Error during the execution of rule java.lang.UnsupportedOperationException: null at …

Is the .intValue what gives you the primitive? Going back to the first command, the add using primitives does work. I guess I just don’t know how to convert a Number.state element into a primitive.

OK. So the first one is telling us that index_val.state is returning an object of type State and it can’t figure out how to convert it to a Number.

The second error is telling us that the State object doesn’t have an intValue method, which shouldn’t surprise us. That is why I wanted to do the “as Number” in the first place.

I’m surprised by the third error.

Hmmmm.

What is the full first line of the log “Error during the execution” log statement? The part after the “null at” may help us know which of the two arguments it doesn’t like. The fact that is is complaining about a null is weird.

Yes, the .intValue method gives you the primitive. And you are successfully converting the Number.state value to a primitive with your cast to DecimalType before calling .intValue. I think something else is going on.

So while the index to the array (first argument to add()) needs to be a primitive, the second value, behind the scenes, is supposed to be automatically converted to an Integer for you. Maybe that isn’t happening. Does this work or does the error change if you change the line to:

theArray.add((index_val.state as DecimalType).intValue, new Integer((SomeValue.state as DecimalType).intValue))

This makes explicit what is supposed to be going on in the background.

This new line:

theArray.add((index_val.state as DecimalType).intValue, new Integer((SomeValue.state as DecimalType).intValue))

Gives me the same error as I was getting before:

2015-10-09 11:50:31.570 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during
the execution of rule 'arraycalc'
java.lang.UnsupportedOperationException: null
        at java.util.AbstractList.add(Unknown Source) ~[na:1.8.0_60]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.
0_60]
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.
0_60]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:
1.8.0_60]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_60]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOpera
tion(XbaseInterpreter.java:729) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._featureCal
lOperation(XbaseInterpreter.java:713) ~[na:na]
        at sun.reflect.GeneratedMethodAccessor40.invoke(Unknown Source) ~[na:na]

        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:
1.8.0_60]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_60]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispat
cher.java:291) ~[na:na]
        at org.openhab.model.script.interpreter.ScriptInterpreter.internalFeatur
eCallDispatch(ScriptInterpreter.java:69) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateMe
mberFeatureCall(XbaseInterpreter.java:549) ~[na:na]
        at sun.reflect.GeneratedMethodAccessor44.invoke(Unknown Source) ~[na:na]

        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:
1.8.0_60]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_60]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispat
cher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEva
luate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._evaluateBl
ockExpression(XbaseInterpreter.java:321) ~[na:na]
        at sun.reflect.GeneratedMethodAccessor46.invoke(Unknown Source) ~[na:na]

        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:
1.8.0_60]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_60]
        at org.eclipse.xtext.util.PolymorphicDispatcher.invoke(PolymorphicDispat
cher.java:291) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEva
luate(XbaseInterpreter.java:218) ~[na:na]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(Xb
aseInterpreter.java:204) ~[na:na]
        at org.openhab.model.script.internal.engine.ScriptImpl.execute(ScriptImp
l.java:59) ~[na:na]
        at org.openhab.core.scriptengine.ScriptExecutionThread.run(ScriptExecuti
onThread.java:44) ~[na:na]

Is there some other pre-req I’m supposed to have imported?

import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.util.List
import java.util.ArrayList
import org.openhab.core.library.types.DecimalType.*

Thanks for the help!

Edit:
I’ve tried

theArray.add(2, new Integer((SomeValue.state as DecimalType).intValue))

theArray.add(new Integer((SomeValue.state as DecimalType).intValue), 4)

Both gave me the same error, but passed Designer’s checks.

Hmmm. OK, lets do some basic checks.

Let’s make sure the items are actually set:

logInfo("arraycalc", "index_val = " + index_val.state.toString)
logInfo("arraycalc", "SomeValue = " + SomeValue.state.toString)

Make sure the logs show the values you expect.

Now we will try to convert them to primitives:

logInfo("arraycalc", "Converting index")
val index = (index_val.state as DecimalType).intValue
logInfo("arraycalc", "Converting value")
val value = (SomeValue.state as DecimalType).intValue

If we made it this far we can see if the primitives work:

logInfo("arraycalc", "Adding value to array, primitives")
theArray.add(index, value)

Finally, lets see if we can do it inline:

logInfo("arraycalc", "Adding value to array, casting index")
theArray.add((index_val.state as DecimalType).intValue, value)

logInfo("arraycal", "Adding value to array, casting value)
theArray.add(index, (SomeValue.state as DecimalType).intValue)

Somewhere the rule will crash with probably the same error. Seeing where it crashes will tell us what the problem is.

I’m willing to bet that either index_val or SomeValue isn’t set. To me that makes the most sense to why it isn’t working.

Designer will complain about unknown symbols if you are missing an import.

OK.

I got to this line, and then the same error is thrown.

theArray.add(index, value)

I tried to make sure index and value have actual values by displaying them on a Number item in my sitemap. And they are indeed numbers, and index is set to 2 via assignment.

Even if I do this below, I get that same error at run time.

val int[] theArray = newArrayList(1, 2, 3, 4, 5, 6, 7, 8)
....
val index=2
val value=3
theArray.add(index, value)

Where as this line below works fine.
theArray.add(2, 3)

Is there something weird with my array declaration? Am I creating an array of integers as I had intended? I’ve also tried

var Integer index=2
var Integer value=3

but same error.

Yes. The DSL the rules engine does not support straight arrays (though it appears to support somewhat more than I thought). You have to declare your array as:

val List<Integer> theArray = newArrayList(1, 2, 3, 4, 5, 6, 7, 8)

I thought you had already done that per my reply above.

3 Likes

Aahh, you’re absolutely right! Sorry about that, I had not made that change from your reply. That fixed everything.

Thank you!

1 Like