groovyclosuresdsl

Setting closure's delegate in method results in SO


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


Solution

  • 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