Setting closure's delegate in method results in SO

54 Views Asked by At

I'm using the following code with builder:

Closure getObject = { String oType ->
  return {
    type oType
    format 'int32'
  }
}

def yaml = new YamlBuilder()
yaml{
  string 'value'
  object1{
    Closure type = getObject 'integer'
    type.delegate = delegate
    type()
  }
  object2{
    Closure type = getObject 'long'
    type.delegate = delegate
    type()
  }
}

println yaml.toString()

The code works as expected and produces correct yaml.

Now I want to remove repeating lines of code and introduced the inflate() method:

def inflate( Closure closure, delegate ) {
  closure.delegate = delegate
  closure.resolveStrategy = Closure.DELEGATE_FIRST
  closure()
}

so that my DSL gets more compact:

yaml{
  string 'value'
  object1{
    inflate getObject( 'integer' ), delegate
  }
  object2{
    inflate getObject( 'long' ), delegate
  }
}

This led to the SOE:

java.lang.StackOverflowError
    at Script1$_run_closure1$_closure3.doCall(Script1.groovy:5)
    at Script1$_run_closure1$_closure3.doCall(Script1.groovy)

What am I missing?

The script can be played with on appSpot

2

There are 2 best solutions below

0
injecteer On BEST ANSWER

It turns out, that instead of a method a Closure should be used.

In this case the closure nesting and reuse is not a problem any more:

import groovy.yaml.YamlBuilder

Closure inflate = { Closure closure, del ->
  closure.delegate = del
  closure()
}

Closure getObject = { String oType ->
  return {
    type oType
    format 'int32'
  }
}

Closure getNestedObject = {
  return {
    type 'object'
    nested{
      inflate getObject( 'nested' ), delegate     
    }
  }
}

def yaml = new YamlBuilder()

yaml{
  string 'value'
  object1{
    inflate getObject( 'integer' ), delegate
  }
  object2{
    inflate getNestedObject(), delegate    
  }
}

println yaml.toString()

prints

---
object1:
  type: "integer"
  format: "int32"
object2:
  type: "object"
  nested:
    type: "nested"
    format: "int32"

The runnable code is here

1
tim_yates On

Not 100% sure what's going on, but this seems to work


Closure getObject = { String oType, del ->
  { ->
    type oType
    format 'int32'
  }.tap { 
      it.delegate = del
      it.resolveStrategy = Closure.DELEGATE_FIRST
  }
}

def yaml = new YamlBuilder()
yaml{
  string 'value'
  object1{
    getObject('integer', delegate)()
  }
  object2{
    getObject('long', delegate)()
  }
}