Recently while debugging our grails application I saw something like this:
variable.findAll { it.condition }
Since the debugger told me that this variable was null, I happily thought I might have found my little bug and moved on waiting for the big crash and a nice NullPointerException. Instead I got an empty list.
Wait WAT?
That seemed rather bizarre to me, so I checked it inside a groovy shell.
null.findAll { true } ==> []
null.findAll { false } ==> []
null.findAll { it } ==> []
null.findAll {} ==> []
null.findAll() ==> []
Ok, what’s going on here? First thing to note is that Groovie’s null is an object.
null.getClass().name ==> org.codehaus.groovy.runtime.NullObject
From the Java perspective kind of surprising but since I’m familiar with Ruby it was somehow expectable. So far no magic, but where does that findAll
method come from?
null.getMetaClass().methods*.name
==> [equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait, getMetaClass, getProperty, invokeMethod, setMetaClass, setProperty, asBoolean, asType, clone, equals, getNullObject, getProperty, hashCode, invokeMethod, is, iterator, plus, setProperty, toString]
Not there… so Groovy-Voodoo. Luckily there are some developers more experienced with Groovy than me in our company (even one who contributed to it some time ago), so I could ask someone else than Google. We got the source code (Groovy 1.8 in our case) and dug into it. The place where a lot of those magical methods dwell is DefaultGroovyMethods.java
. According to the documentation the static methods in this class will be available in the class of each method’s first parameter. So here we found the following:
public static Collection findAll(Object self, Closure closure) {
List answer = new ArrayList();
Iterator iter = InvokerHelper.asIterator(self);
return findAll(closure, answer, iter);
}
Which at least explains why that is available for null
. Furthermore it shows us that findAll
should work on any Object, too. A quick check in the console confirms this.
new Object().findAll { true } ==> [java.lang.Object@79ad86e9]
However it does not explain how the invocation works and why the result is []
. So what’s happening here? The asIterator
method simply invokes a method named iterator
on self. Groovie’s NullObject
defines this particular method in the following way:
public Iterator iterator() {
return Collections.EMPTY_LIST.iterator();
}
This clearly explains why we get an empty list from our findAll
call. In the case of an arbitrary GroovyObject
we again find (after an unsuccessful lookup in GroovyObject.java
) the iterator method for objects in the DefaultGroovyMethods
class simply putting the object into a collection and iterating over it.
public static Iterator iterator(Object o) {
return DefaultTypeTransformation.asCollection(o).iterator();
}
What is still missing to a full understanding of this phenomenon is how those default groovy methods get invoked. Covering this would be way beyond the scope of this blog post. If you browse around a little in the source all this meta stuff can get kind of overwhelming. What we can take out of this so far (beside getting confused by Groovie’s method invocation mechanisms) is some more awareness to the fact that anything and everything can happen in languages such as Groovy even when it all starts with an innocent null object…