amarillion
amarillion

Reputation: 24907

Groovy scope - how to access script variable in a method

I have a question about scoping rules in Groovy. In the following snippet, I have three variables, a has local scope, b has script scope, and c should get script scope as well using the @Field annotation.

#!/usr/bin/groovy
import groovy.transform.Field;

//println org.codehaus.groovy.runtime.InvokerHelper.getVersion()

def a = 42;
b = "Tea"
@Field def c = "Cheese"

void func()
{
    // println a // MissingPropertyException
    println b // prints "Tea"
    println c // prints "Cheese" with groovy 2.1.2, MissingPropertyException with groovy 1.8.6

}

class Main
{
    def method()
    {
        // println a // MissingPropertyException
        // println b // MissingPropertyException
        // println c // MissingPropertyException with both 1.8.6. and 2.1.2
    }

}

func();
new Main().method();

I get MissingPropertyExceptions on the lines indicated with comments. The exceptions on a are expected, as that variable has local scope. But I would expect b to be accessible inside method() - it isn't. @Field doesn't do anything in groovy 1.8.6, although after upgrading it works, so I guess that is an old bug. Nevertheless, c is inaccessible inside method() with either version.

So my questions are:

  1. Why can't I access a variable annotated with @Field inside method()?
  2. How can I refer to a script variable inside method()?

Upvotes: 41

Views: 52415

Answers (2)

ataylor
ataylor

Reputation: 66059

When you have methods or statements outside of a class declaration in a groovy script, an implicit class is created. To answer your questions:

  1. In your example, func() can access the field c because they are both members of the implicit class. The Main class is not, so it can't.

  2. You need to pass a reference to the script variable to method(). One way is to pass the implicitly defined binding object, through which you can access all the script scope variables.

Example:

#!/usr/bin/groovy
import groovy.transform.Field;

//println org.codehaus.groovy.runtime.InvokerHelper.getVersion()

def a = 42;
b = "Tea"
@Field def c = "Cheese"

void func()
{
    // println a // MissingPropertyException
    println b // prints "Tea"
    println c // prints "Cheese" with groovy 2.1.2, MissingPropertyException with groovy 1.8.6

}

class Main
{
    def scriptObject
    def binding

    def method()
    {
        // println a // MissingPropertyException
        println binding.b
        println scriptObject.c
    }
}

func();
new Main(scriptObject: this, binding: binding).method();

Upvotes: 40

tim_yates
tim_yates

Reputation: 171054

This script and Main are generated as two separate classes inside the same file.

As Main is not an internal class of the Script class, it cannot see the java.lang.Object c field inside the script class.

You would either have to explicitly wrap this script in a class with a static main( args ) method (and an internal Main class) or you would need to pass an instance of the script class to the method like: Main.method( this )

This is the sort of thing that the above script generates:

class Script032034034 {
  Object c

  Script032034034() {
    c = 'Cheese'
  }

  Object run() {
    Object a = 42
    b = 'Tea'
    func()
    new Main().method()
  }

  void func() {
    println b
    println c
  }
}

class Main {
  Object method() {
  }
}

Upvotes: 11

Related Questions