Reputation: 277
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
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