uros calakovic
uros calakovic

Reputation: 277

Pass a list as an argument to Groovy BuilderSupport createNode

I have a Groovy class that creates another classes dynamically:

package javainterop3

import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;

class DynamicClass {

    GroovyClassLoader loader
    String name
    Class cls
    def imports
    def fields
    def methods

    def DynamicClass() {
        imports = []
        fields = [:]
        methods = [:]
    }

    def setLoader(GroovyClassLoader loader)
    {
        this.loader = loader
    }

    def setName(String name) {
        this.name = name
    }

    def setImports(Set imports) {
        this.imports = imports.each{importClass ->
            "${importClass.getPackage().getName()}" +
                ".${importClass.getSimpleName()}"
        }
    }

    def addFields(Map fields) {
        this.fields = fields
    }

    def addMethods(Map methods) {
        this.methods = methods
    }

    def createClass() {

        def templateText = '''
<%imports.each {%>import $it\n <% } %> 
class $name
{
<%fields.each {%>    $it.value $it.key \n<% } %>
}
'''
        fields.each {
            it.value = it.value.simpleName
        }

        def data = [name: name, imports: imports, fields: fields]

        def engine = new groovy.text.SimpleTemplateEngine()
        def template = engine.createTemplate(templateText)
        def result = template.make(data)
        println result.toString()
        cls = loader.parseClass(result.toString())
        methods.each {
            cls.metaClass."$it.key" = it.value
        }
    }
}

I am now trying to create a simple builder for new class creation:

package javainterop3

import java.util.Map;

class ClassBuilder extends BuilderSupport{

    private DynamicClass dynamicClass

    @Override
    protected void setParent(Object parent, Object child) {
    }

    @Override
    protected Object createNode(Object name) {
        if(name == 'newClass')
        {
            dynamicClass = new DynamicClass()
        }
        else
        {
            throw new IllegalArgumentException(name)
        }
    }

    @Override
    protected Object createNode(Object name, Object value) {

        if(name == 'loader')
        { 
            dynamicClass.setLoader(value)
        }
        else if(name == 'name')
        {
            dynamicClass.setName(value)
        }
        else if(name == 'imports')
        {
            println value
            dynamicClass.setImports(value)
        }
        else
        {
            throw new IllegalArgumentException(name)
        }
    }

    @Override
    protected Object createNode(Object name, Map attributes) {
        if(name == 'fields')
        {
            dynamicClass.setFields(attributes)
        }
        else if(name == 'methods')
        {
            dynamicClass.setMethods(attributes)
        }
        else
        {
            throw new IllegalArgumentException(name)
        }
    }

    @Override
    protected Object createNode(Object name, Map attributes, Object value) {
        return null
    }

    @Override
    protected void nodeCompleted(Object parent, Object node)
    {
        if(node instanceof DynamicClass)
        {
            node.createClass()
        }
    }

    public Class getDynamicClass()
    {
        return dynamicClass.cls
    }

}

Here is a test script that creates a simple Groovy class:

package javainterop3

import java.util.Calendar
import java.util.Random

def builder = new ClassBuilder()

builder.newClass{

    loader this.class.classLoader

    name 'MyClass'

    imports Calendar, Random, UUID

    fields 'field1' : Integer,
           'field2' : Integer

    methods 'sum' : {return field1 + field2},
            'product' : {return field1 * field2},
            'testCalendar' : {return Calendar.getInstance().getTime()},
            'testRandom' : {return (new Random()).nextInt()}

}

My problem is with the imports method. It is supposed to accept a list of Java classes to import. In the underlying class (DynamicClass) imports is a list and I am not sure how to pass a list to the builder or in which createNode() overload to handle the imports method. I thought since the argument is not a map to handle it in createNode(Object name, Object value), but I get this exception:

Caught: groovy.lang.MissingMethodException: No signature of method: \
  javainterop3.Test.imports() is applicable for argument types:     \
  (java.lang.Class, java.lang.Class, java.lang.Class) values:       \
  [class java.util.Calendar, class java.util.Random, ...]

'loader' and 'name' are intercepted in createNode(Object name, Object value) and 'fields' and 'methods' in createNode(Object name, Map attributes), but I'm not sure how to handle 'imports' which is a list. I have trying it to each createNode overload but haven't been able to make it work.

Upvotes: 1

Views: 518

Answers (1)

dmahapatro
dmahapatro

Reputation: 50265

It does not match with the overridden methods. One way is to have below modifications:

//Dynamic Class
def setImports(Class importClass) {
    this.imports << "$importClass.name"
}

and then while building

builder.newClass{

    loader this.class.classLoader

    name 'MyClass'

    [Calendar, UUID, Random].each { imports it }

    fields 'field1' : Integer,
           'field2' : Integer

    methods 'sum' : {return field1 + field2},
            'product' : {return field1 * field2},
            'testCalendar' : {return Calendar.getInstance().getTime()},
            'testRandom' : {return (new Random()).nextInt()}

}

Upvotes: 1

Related Questions