Callid
Callid

Reputation: 79

How do I get the class (scope) from which a closure is called in Groovy?

class Box {
    Closure click

    Box () {
        click = {}
    }

    void onClick() {
        click()
    }
}

class TextBox extends Box {
    List<String> content

    TextBox (String[] a) {
        super()
        content = a
    }
}

class Main {
    public static void main(String[] args) {
        Main m = new Main()
    }

    Main() {
        String[] a = ["Hello world!"]
        Box b = new TextBox(a)
        b.click = {content.add("You clicked this box!")}
        b.onClick()  //throws Exception
    }
}

(The above is, obviously, a simplification; in reality, the classes are a bit more involved, and calling of onClick() is due to a click on a JFrame)

Now, when I try to run that (i.e. run Main.main()), I get an exception: Exception in thread "AWT-EventQueue-0" groovy.lang.MissingPropertyException: No such property: content for class: Main

Clearly, it is, for some reason, searching for the List in Main, not in TextBox or Box, from where it is called. I also tried using this, owner and delegate, but they all point to Main as well. I managed to have it work by giving this as an argument:

...
void onClick() {
    click(this)
}
...
b.click = {it.content.add("You clicked this box!")}

It seems, however, weird to actually need to pass "this" to a closure just for it to be able to know where it was called from. Isn't there a more elegant solution? Also, even if it is indeed impossible to get into the TextBox-scope, is it somehow possible to get into the Box-scope?

Upvotes: 0

Views: 216

Answers (2)

Mr. Cat
Mr. Cat

Reputation: 3552

See closures groovy docs. Pay attention on Implicit variables: this, owner, and delegate.
Edit: Fix delegate before call:

b.click = {content.add("You clicked this box!")}
b.click.delegate = b

Upvotes: 1

dmahapatro
dmahapatro

Reputation: 50245

Main() {
    String[] a = ["Hello world!"]
    Box b = new TextBox(a)
    println "1:- $b.click.delegate" //Will print TextBox
    b.click = {
        println "2:- $b.click.delegate" //Will print Main

        //Since the closure is now defined inside Main(), 
        //Main becomes the delegate. Reset the delegate to TextBox.
        b.click.delegate = b

        println "3:- $b.click.delegate" //Will print TextBox
        content.add("You clicked this box!")
    }
    println "4:- $b.click.delegate" //Will print Main
    b.onClick()

    println b.content //Will print [Hello world!, You clicked this box!]
}

//Output:
1:- TextBox@c166770
4:- Main@6dbdc863
2:- Main@6dbdc863
3:- TextBox@c166770
[Hello world!, You clicked this box!]

Points to note:

  • Sequence 4 is present before 2 and 3 since the closure is not called/invoked at that point of time.
  • Sequence 1 prints TextBox but Sequence 4 prints Main. Note that the delegate has changed (after defining closure b.click) from TextBox to Main by now.

To make things easier, instead of changing things in Main, you can just modify onClick() as

void onClick() {
    click.delegate = this
    click()
}

Refer this script for details.

Upvotes: 1

Related Questions