How to Create Nested Builder Based DSLs on Groovy using FactoryBuilderSupport

I want to create a nested builder. Something like:

meta('JSON') {
   // JSON content using JSON Builder in this case
}
content('HTML') {
   // HTML content using MarkupTemplateEngine in this case
}

I don't want to have

JsonBuilder builder = new JsonBuilder()
builder.author {
}

or

def builder = new groovy.xml.MarkupBuilder()
builder.html {
}

How can initial builder and the nested builder be setup?


I have defined the DSL common elements as:

import groovy.transform.CompileStatic
import org.eclipse.collections.impl.map.mutable.UnifiedMap

@CompileStatic
abstract class DSLElement extends UnifiedMap {
    DSLElement(Map attributes) {
        putAll(attributes)
    }

    getAt(Object key) {
        get(key)
    }

    putAt(Object key, Object value) {
        put(key, value)
    }
}

@CompileStatic
class BaseFactoryBuilder extends FactoryBuilderSupport {
    BaseFactoryBuilder(boolean init = true) {
        super(init)
    }
}

@CompileStatic
class DSLFactoryBuilder
        extends BaseFactoryBuilder
        implements MetaRegistration,
                ContentRegistration,
                TemplateRegistration {

}

Individual elements I have defined as:

import groovy.transform.CompileStatic
import groovy.transform.InheritConstructors
import groovy.transform.SelfType

@CompileStatic
@InheritConstructors
class Meta extends DSLElement {}

@CompileStatic
class MetaFactory extends AbstractFactory {
    boolean isLeaf() {
        return false
    }

    Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
            throws InstantiationException, IllegalAccessException {
        return new Meta(attributes)
    }
}

@SelfType(DSLFactoryBuilder)
@CompileStatic
trait MetaRegistration {
    registerMeta() {
        registerFactory("meta", new MetaFactory())
    }
}

and

import groovy.transform.CompileStatic
import groovy.transform.InheritConstructors
import groovy.transform.SelfType

@CompileStatic
@InheritConstructors
class Content extends DSLElement {
}

@CompileStatic
class ContentFactory extends AbstractFactory {
    boolean isLeaf() {
        return false
    }

    Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes)
            throws InstantiationException, IllegalAccessException {
        return new Content(attributes)
    }
}

@SelfType(DSLFactoryBuilder)
@CompileStatic
trait ContentRegistration {
    registerMeta() {
        registerFactory("content", new ContentFactory())
    }
}

I have not figured you how to do the nesting another builder and the parameter 'JSON' or 'HTML'.

I am following the FactoryBuilderSupport example given in: Groovy for Domain-Specific Languages

Upvotes: 0

Views: 277

Answers (1)

daggett
daggett

Reputation: 28599

maybe i did not fully understand the question - it's too long to read and code you provided not runnable (or not full)

i assume you want to achieve this:

def html = meta('HTML'){
    person(name:'foo')
}

def json = meta('JSON'){
    person(name:'bar')
}

if so, here is a code to implement it

String meta(String ctxName, Closure builderBody){
    def builder = null
    def writer = null
    if(ctxName=='HTML'){
        writer=new StringWriter()
        builder=new groovy.xml.MarkupBuilder(writer)
    }else if(ctxName=='JSON'){
        builder=new groovy.json.JsonBuilder()
    }
    else throw new RuntimeException("unsupported meta=`$ctxName`")
    
    def result = builder.with(builderBody)
    
    if(ctxName=='HTML'){
        return writer.toString()
    }else if(ctxName=='JSON'){
        return builder.toPrettyString()
    }
}


println meta('HTML'){
    person(name:'foo')
}

println meta('JSON'){
    person(name:'bar')
}

result:

<person name='foo' />
{
    "person": {
        "name": "bar"
    }
}

Upvotes: 1

Related Questions