Reputation: 277
Given a class name I would like to dynamically create a Groovy class add properties and methods to it. I create the new class using
instance = this.class.classLoader.parseClass(
"public class $name {}")
For methods I use
instance.metaClass."$it.key" = it.value
where it.key is a string (method name) and it.value is a closure. This is convenient because I can specify method parameter types and get type checking. However, I am not able to specify a dynamically created property type without assigning it a value. I can work around this by explicitly defining getter and setter for the property. This works, but it seems that neither metaClass.name = value nor metaClass.getName = {} actually create a field in the class because the Java field operator doesn't work for the created properties. Can I ad a property to a Groovy class and specify its type without assigning an initial value to it or explicitly defining getter and setter methods? Is there a way to add a new field to a Groovy class? Here is the script:
class SomeClass {
Integer p1
String p2
}
class ClassBuilder {
def name
def instance
def properties
def methods
def ClassBuilder() {
properties = [:]
methods = [:]
}
def set_name(name) {
this.name = name
}
def add_property(name, type) {
properties[name] = type
}
def add_method(name, closure) {
methods[name] = closure
}
def get_instance() {
instance = this.class.classLoader.parseClass(
"public class $name {}")
properties.each {
instance.metaClass."$it.key" = null
//doesn't work
instance.metaClass."$it.key".type = it.value
}
methods.each {
instance.metaClass."$it.key" = it.value
}
return instance
}
}
builder = new ClassBuilder()
builder.set_name('MyClass')
builder.add_property('property1', String)
builder.add_property('property2', SomeClass)
builder.add_method('method1', {SomeClass obj -> println obj})
builder.add_method('setProperty2', {SomeClass obj -> this.property2 = obj})
builder.add_method('getProperty2', {return this.property2})
builder.add_method('method2', {return property1 + property2})
c = builder.get_instance()
i = c.newInstance()
i.property1 = new SomeClass()
i.property2 = 5
//i.method2() //throws GroovyCastException
//i.property2 = 'throws GroovyCastException'
//i.@property1 = 'throws MissingFieldException'
//No such field: property2 for class: MyClass
//i.@property2 = new SomeClass()
i.method1(new SomeClass())
//i.method1('throws MissingMethodException')
[Edit]
The use case is like this: I define an interface or a base class in Java. A user implements the interface or extends the base class in Groovy and the class is passed back to Java to be used by the main application. Users are not programmers so they define the class using a simple DSL and I construct the actual class using a builder. I am still experimenting with Groovy/JRuby and Java interop (new to both languages).
Upvotes: 3
Views: 10589
Reputation: 41
a nice variant would be to use the GroovyShell.
def c = """
package de.myCorp.test
class Test {
def a
def b
def c
Test(String c){ this.c = c}
public String greet(){
return "hello "+a
}
public void count(){
(1..4).each{
println it
}
}
}
def class UsingTest {
Test mytest = null
UsingTest (Test test){ this.mytest = test }
def using(){
mytest.greet();
}
}
"""
GroovyShell gs = new GroovyShell()
//I hope this is not a too bad hack ^^
def erg = gs.evaluate(c+";['test':Test, 'using':UsingTest];")
def testclass = erg["test"].newInstance("Charlotte on Ice")
testclass.a = "hugo"
testclass.b = "sepp"
testclass.count()
assert testclass.greet() == "hello hugo"
assert testclass.c == "Charlotte on Ice"
assert testclass.class.name == "de.myCorp.test.Test"
def usingclass = erg['using'].newInstance(testclass)
usingclass.mytest.a = "Fritzl"
assert usingclass.using() == "hello Fritzl"
...and especially notice the GroovyShell.evaluate(URI uri) types... GroovyShellDoc
Upvotes: 2
Reputation: 277
I have more or less been able to get it working by using GroovyClassLoader and SimpleTemplateEngine. here is the code:
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
}
}
and here is an example of how I use it to create a class dynamically:
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()
Upvotes: 6