Java – dynamically create groovy classes
Given a class name, I want to dynamically create a groovy class and add properties and methods to it I use to create new classes
instance = this.class.classLoader.parseClass( "public class $name {}")
For the method I use
instance.MetaClass."$it.key" = it.value
Including it Key is a string (method name), it Value is a closure This is convenient because I can specify method parameter types and get type checks However, I cannot specify a dynamically created property type without assigning a value to it I can solve this problem by explicitly defining getters and setters for properties This works, but it seems metaclass Name = value and metaclass Getname = {} does not actually create a field in the class because the Java field operator is not applicable to the created property Can I add a property to a groovy class and specify its type without assigning it an initial value or explicitly defining getter and setter methods? Is there any way to add new fields to groovy classes? This 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 base class in Java The user implements the interface or extension base class in groovy and passes the class back to Java for use by the main application Users are not programmers, so they use simple DSL to define classes and use builders to construct actual classes I'm still trying to use groovy / jruby and Java interop (both languages are new)
Solution
By using groovy classloader and simpletemplateengine, I can make it work more or less This 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 } }
Here is an example of how I use it to dynamically create classes:
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()