Click here to Skip to main content
15,888,351 members
Articles / General Programming
Tip/Trick

Create Groovy Classes Dynamically

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
29 Apr 2014CPOL2 min read 19K   92   4  
Create Groovy classes at run-time and use them from Groovy or Java

Introduction

Using GroovyClassLoader.parseClass(), it is possible to create a new Groovy class dynamically at run-time and use it from a Groovy script or a Java application. GroovyClassLoader.parseClass() will parse a string passed to it and attempt to create a Groovy class. This way, it is possible to add the necessary imports, set the class name and add the fields to the class being created. Groovy automatically adds field getters and setters - if a field name is field1, getField1() and setField1() are generated. Adding methods is also possible this way, but it can also be done later by assigning closures to the created class MetaClass. This is convenient because parsing is avoided. Closure parameters become method parameters.

Creating the Class

The ClassBuilder.groovy file is as follows:

Java
 package javainterop2

class ClassBuilder {

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

    def ClassBuilder(GroovyClassLoader loader) {
        this.loader = loader
        imports = []
        fields = [:]
        methods = [:]
    }

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

    def addImport(Class importClass) {
        imports << "${importClass.getPackage().getName()}" +
                ".${importClass.getSimpleName()}"
    }

    def addField(String name, Class type) {
        fields[name] = type.simpleName
    }

    def addMethod(String name, Closure closure) {
        methods[name] = closure
    }

    def getCreatedClass() {

        def templateText = '''
<%imports.each {%>import $it\n <% } %> 
class $name
{
<%fields.each {%>    $it.value $it.key \n<% } %>
}
'''
        def data = [name: name, imports: imports, fields: fields]

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

Using the Class from Groovy

The test.groovy file in the same package:

Java
 package javainterop2

import java.util.Calendar
def builder = new ClassBuilder(this.class.classLoader)
builder.setName("MyClass");

builder.addImport(Calendar)

builder.addField('field1', Integer)
builder.addField('field2', Integer)

builder.addMethod('sum') { field1 + field2 }

builder.addMethod('product') { field1 * field2 }

builder.addMethod('testCalendar') {
    println Calendar.getInstance().getTime()
}

Class myClass = builder.getCreatedClass()
def myInstance = myClass.newInstance()

myInstance.field1 = 1
myInstance.field2 = 2

println myInstance.sum()
println myInstance.product()

myInstance.setField2(1500)
println myInstance.getField2()

myInstance.testCalendar()

The class created by GroovyClassLoader is stored in a variable and can be instantiated using myClass.newInstance(). field2 can be accessed directly or using its setter setField2(). The java.util.Calendar class is imported and used in the class' testCalendar() method.

Using the Class from Java

Using a dynamically generated class from Java is not so straightforward. The class can be passed from Groovy to Java as a variable. test1.groovy creates another Groovy class:

Java
 package javainterop2

static Class getDynamicClass(ClassLoader loader) {
    def builder = new ClassBuilder(new GroovyClassLoader(loader))
    builder.setName('AnotherClass')
    builder.addField('field1', Integer)
    builder.addField('field2', Integer)
    builder.addMethod('sum') { return field1 + field2 }

    return builder.getCreatedClass()
}

and in Java:

Java
 package javainterop2;

import groovy.lang.GroovyObject;
import groovy.lang.GroovyShell;

import java.io.File;
import java.io.IOException;

import org.codehaus.groovy.control.CompilationFailedException;

public class Interop {

    public static void main(String[] args) {

        try {
            File file = new File("src/javainterop2/test1.groovy");
            GroovyShell shell = new GroovyShell();
            Class<?> AnotherClass = (Class<?>) shell.parse(file).invokeMethod(
                    "getDynamicClass", GroovyShell.class.getClassLoader());
            System.out.println(AnotherClass);
            try {
                GroovyObject o = (GroovyObject) AnotherClass.newInstance();
                o.setProperty("field1", 1);
                o.setProperty("field2", 2);
                Object[] arguments = {};
                System.out.println(o.invokeMethod("sum", arguments));
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (CompilationFailedException | IOException e) {
            e.printStackTrace();
        }
    }
}

test1.groovy is parsed by GroovyShell and getDynamicClass() is invoked to get the class variable. The new class instance is cast to GroovyObject and GroovyObject.setProperty(), GroovyObject.getProperty() and GroovyObject.invokeMethod() are used to manipulate the instance.

The Example Project

I added the Eclipse project I used for testing that contains all the files listed above. The project references groovy-all-2.2.2.jar which is located in the project's lib directory. The file is a part of the standard Groovy distribution (in the groovy/2.2.2/embeddable directory on my machine) and is used for invoking Groovy scripts from Java. Eclipse is Kepler Service Release 2.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems / Hardware Administrator
Bosnia and Herzegovina Bosnia and Herzegovina
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --