1

we want to implement an infrastructure for Samsung Groovy SmartThings. The major part for the infrastructure of course is to implement different classes for each device with their corresponding methods. For example for locks' devices, we assumed that we have a lock class with methods lock() and unlock(). The problem here is that we have this part of code in one of the SmartThings applications which are in Groovy:

def presence(evt)
{
    if (evt.value == "present") {
            //Somecode
            lock1.unlock()
    }
    else {
            //Somecode
            lock1.lock()
    }
}

So most probably, lock1 is the object for class lock, and lock() and unlock() are methods for that class. Here is the thing: Using lock1[0].unlock() command unlocks the door lock number #0, but using lock1.unlock() command unlocks all the door locks.

The question here is that how the class is created? If lock1 is a list of objects, how we can have a command like lock1.unlock().

The point here is that both of the objects should have the same name lock1, and both of the methods are the same method named lock().

Thank you in advance.

AJ_IoT
  • 13
  • 4

2 Answers2

1

The "problem" to be seen here is the (implicit) spread operator in Groovy.

The expression cars*.make is equivalent to cars.collect{ it.make }. Groovy’s GPath notation allows a short-cut when the referenced property isn’t a property of the containing list, in that case it is automatically spread. In the previously mentioned case, the expression cars.make can be used, though retaining the explicit spread-dot operator is often recommended.

(the example there speaks of GPath, but the same is true for lists, maps, ...)

So lock1.unlock() is lock1*.unlock() in this case. (Dynamic) Groovy will see, that there is no unlock() method on a list and just fans out.

As for "So most probably, lock1 is the object for class lock", will give you nightmares. Don't guess - find out. You can println(lock1.inspect()) for details and hopefully the author of that class had the foresight to add a useful toString() method. Check the docs, what the function returns, you assign locks1 from. Use tooling, that tells you the type (IDE, debugger, ...).

For the developers better naming here helps:

// BAD naming
def locks1 = smartThings.getAll(Lock, clientId)
// better naming, add the type if it helps you or your IDE
Collection<Lock> allLocks = smartThings.getAll(Lock, clientId)

Now if you call allLocks.lock(), it's way more obvious, what is going on.

To prevent the implicit spread operator, you can use static compilation in your groovy script. E.g.:

class Lock {
    def lock() { println "lock" }
    def unlock() { println "unlock" }
}

@groovy.transform.CompileStatic
class X {
    public static main() {
        [new Lock()].lock()
    }
}

This won't compile:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
x.groovy: 9: [Static type checking] - Cannot find matching method java.util.List#lock(). Please check if the declared type is correct and if the method exists.
 @ line 9, column 3.
                [new Lock()].lock()
     ^

1 error

Using the explicit spread operator ([new Lock()]*.lock()) compiles.

cfrick
  • 35,203
  • 6
  • 56
  • 68
0

You have 2 options here:

1) use Groovy's spread operator to call the method on every element of the list:

List lock1 = [.....]
lock1.*lock()

2) Make a Lock class extend or contain a List of elements, and add a class-level method to it:

class Lock {
  List locks

  def lock() {
    locks.*lock()
  }

  // this method allows for calls like lock1[42].lock()
  def getAt( int ix ) { 
    locks[ ix ] 
  } 
}

Actually, inheritance is BAD for IoT devices.

injecteer
  • 20,038
  • 4
  • 45
  • 89
  • Thank you for your response. In the first place I was thinking about spread operator, but the problem there is that in the source code we do not have lock1*.lock(), and we have lock1.lock() with no *, and we want to make sure that the source code compiles correctly. So the first option does not work. The second option, if you create an object for List locks in class Lock, then lock1[0].lock() or lock1.each{it.lock()} will work, but lock1.lock() still will not work. :/ And the error is: No signature of method: java.util.ArrayList.lock() is applicable for argument types: () values: [] – AJ_IoT Jun 19 '19 at 01:53
  • And if you create object for class Lock, and write lock1.lock(), it will lock all the doors, but then lock1[0].lock() will not work and the error of course is: No signature of method: locking.getAt() is applicable for argument types: (Integer) values – AJ_IoT Jun 19 '19 at 01:58