Create method dynamicaly in Groovy with arguments

679 views Asked by At

I have a requirement for example where I am using SuperCSV to read a csv file and map to object.

SuperCSV has a requirement where if the header specifies a field name "firstName" it needs to have setFirstName() method in the class else it throws an exception.

Now if I use Groovy beans then i don't need to declare all these methods just declaring the variables should work fro SuperCSV.

But I was looking for a more dynamic solution where we don't even have to declare those variables.

For example just have an object created on fly and with groovy's dynamic method creation feature SuperCSV will be able to find the setters.

I looked at various options like Expando,ExpandoMetaClass but these did not server my purpose.

Any answer will be appreciated.

Here is the code snippet that worked for me and can be really helpful in reducing lot of code and unnecessary logic:

    static def testBeanReader()
{
    ICsvBeanReader beanReader = null;
    try 
    {
        beanReader = new CsvBeanReader(new FileReader("src/test.csv"),
                                 CsvPreference.STANDARD_PREFERENCE);                      
    }
    catch(Exception e)      
    {

    }

    final String[] header = VirtualObject.getHeaders();
    final CellProcessor[] processors = VirtualObject.getProcessors();

    //Class c1 = createNewClass()

    //String s = createClass()
    def list = ["name", "age"]
    def c = (new GroovyShell().evaluate(createClass(list)) as Class)
    //println(c.methods.grep {it.name.startsWith("get")})

    GroovyObject groovyObject = (GroovyObject)(beanReader.read(c, header, processors))
    Object[] args = {};
    println(groovyObject.getProperty("name"))

}

     static def createClass(def list)
{
    String classDeclaration = "\nclass Test {\n"
    list.each 
    {
        classDeclaration+="def $it\n"
    } 

    classDeclaration+= """
    }
            return Test.class
                              """
    return  classDeclaration
}

This for supercsv thing but can be used for a generic java object with few changes in syntax

1

There are 1 answers

5
Seagull On BEST ANSWER

Can't you use an Groovy shell, to construct these classes dynamically.

def c = (new GroovyShell().evaluate("""
class Test {
    def fileName // list properties here
}
return Test.class
""") as Class)

println(c.methods.grep {it.name.startsWith("set")})

def m = (c as Class).getMethod("setFileName", [Object] as Class[])
// We, and I suppose SuperCSV, can access method via Reflexion API

It's not secure, but it will create Java compatible class on the fly.

I'm not familiar with SuperCSV, but I think it should use smth like reflexion, when Expando or MetaClass solutions will use Groovy Meta-object protocol.

GroovyShell(or GroovyClassLoader) will be able to create normal class, with working Reflexion.

EDITED: Illustrate dynamic nature of generated class. And that we are creating class source first(as we want, from runtime information, from your CSV headers), and then use it.

def createClassDeclaration()
{
    String classDeclaration = "\nclass Test {\n"
    // Here you can use your runtime information, schema, array of fields, getted from any source.
    10.times {
        classDeclaration+="def field$it\n" // declares def field1, field2, ... etc
    }
    classDeclaration += """
    }
    return Test.class
    """
    println(classDeclaration)
    return classDeclaration
}
def compile(String s)
{
    def c = (new GroovyShell().evaluate(s) as Class)
    def m = (c as Class).getMethod("setField1", [Object] as Class[])
    assert m
    println(c.methods.grep {it.name.startsWith("set")})

}

compile(createClassDeclaration())