Groovy Scripts - Exploring Binding

In a previous post exploring scripting basics, we saw how binding was used to supply command-line arguments to scripts. In this post, let's explore the design of binding further.

class BindingDemo {
    static void main(String[] args) {
        Binding sampleBinding = new Binding()
        println sampleBinding.variables

        sampleBinding.message = "Hello"
        println sampleBinding.variables
        println sampleBinding.message
    }
}

Let's run the code.

➜  groovy BindingDemo.groovy
[:]
[message:Hello]
Hello

In the above code, we start by creating an instance of groovy.lang.Binding. At this moment, we see sampleBinding.variables is an empty map. Adding values to binding is achieved using the dot operator. It would look similar to setting a value into a map.

If you already have your values in a map, you could invoke the constructor of Binding by passing the map instance as follows.

Binding bindingFromMap = new Binding([one: 1, two: 2])
bindingFromMap.three = 3
println bindingFromMap.variables // [one:1, two:2, three:3]

If you invoke the constructor with an argument of type array, the value is stored under the key 'arguments'. This technique is used to pass command-line arguments to scripts.

 Binding bindingWithArgs = new Binding(['first', 'second'] as String[])
 println bindingWithArgs.variables // [args:[first, second]]

To understand the use of binding in a script, let's create a class by extending groovy.lang.Script as follows.

@InheritConstructors
class SampleScript extends Script {

    @Override
    def run() {
        println message
    }
}

class BindingDemo {
    static void main(String[] args) {
        Script script = new SampleScript(new Binding([message: 'Hello']))
        script.run()
    }
}

In the above code, neither there is a local variable named message inside the run method, nor there is an instance variable named message. Hence Groovy will lookup for message in the binding object of the script. Since we have a value for message, it will be made available, and the program would print "Hello".

In the usual scenarios, we would invoke the script using groovy command, which would take care of generating the SampleScript class. As an added advantage, you could also read and evaluate the script from a running Groovy application. To demonstrate this, let's create a script file named sample.script under '/Users/naresha/temp' directory.

sample.script

println message

Now let's read the file 'sample.script' from our applicationBindingDemo and execute the script with the existing binding context as follows.

class BindingDemo {
    static void main(String[] args) {
        Script script = new SampleScript(new Binding([message: 'Hello']))
        script.run()
        // Running a dynamically loaded script
        File scriptFile = new File('/Users/naresha/temp/sample.script')
        script.run(scriptFile, [] as String[])
    }
}

Without the binding, we could not have passed a context into the script we loaded and evaluated at the runtime, and it would have severely limited what we could achieve with the scripts. With the availability of binding, developers get to pass the context information to scripts which they can even load at the runtime.

I have used Groovy version 3.0.5 in this post.