Skip to content

Instantly share code, notes, and snippets.

@k33g
Created August 26, 2015 17:31
Show Gist options
  • Select an option

  • Save k33g/1ff1fe6a228964f9437e to your computer and use it in GitHub Desktop.

Select an option

Save k33g/1ff1fe6a228964f9437e to your computer and use it in GitHub Desktop.
dynamicClass.golo
module frankenstein
import gololang.Adapters
struct dynamicClass = {
className,
fields,
methods,
_ctClass
}
augment dynamicClass {
function field = |this, fieldName, fieldType| {
this: fields(): put(fieldName, [fieldType, true])
return this
}
function privateField = |this, fieldName, fieldType| {
this: fields(): put(fieldName, [fieldType, false])
return this
}
function implements = |this, methodName, typeReturn, arguments, method| {
this: methods(): put(methodName, [typeReturn, arguments, method, false])
return this
}
function overrides = |this, methodName, typeReturn, arguments, method| {
this: methods(): put(methodName, [typeReturn, arguments, method, true])
return this
}
function toAdapter = |this| {
# helpers
let resolveCtClass = |clazz| {
let pool = javassist.ClassPool.getDefault()
return pool: get(clazz: getName())
}
# what about boolean getters and setters ?
let generateGetter = |declaringClass, fieldName, fieldClass| {
let getterName = "get" + fieldName: substring(0, 1): toUpperCase()
+ fieldName: substring(1)
let sb = java.lang.StringBuffer()
sb: append("public ")
: append(fieldClass: getName())
: append(" ")
: append(getterName)
: append("(){")
: append("return this.")
: append(fieldName)
: append(";")
: append("}")
let getterMethod = javassist.CtMethod.make(sb: toString(), declaringClass)
return getterMethod
}
let generateSetter = |declaringClass, fieldName, fieldClass| {
let setterName = "set" + fieldName: substring(0, 1): toUpperCase()
+ fieldName: substring(1)
let sb = java.lang.StringBuffer()
sb: append("public void ")
: append(setterName)
: append("(")
: append(fieldClass: getName())
: append(" ")
: append(fieldName)
: append(")")
: append("{")
: append("this.")
: append(fieldName)
: append("=")
: append(fieldName)
: append(";")
: append("}")
let setterMethod = javassist.CtMethod.make(sb: toString(), declaringClass)
return setterMethod
}
this: fields(): each(|fieldName, fieldEntry| { # fieldEntry [fieldType, true] (true=public)
let field = javassist.CtField(resolveCtClass(clazz= fieldEntry: get(0)), fieldName, this: _ctClass())
if fieldEntry: get(1) { field: setModifiers(javassist.Modifier.PUBLIC()) }
this: _ctClass(): addField(field)
if fieldEntry: get(1) is false { # private
# add getter
this: _ctClass(): addMethod(generateGetter(
declaringClass= this: _ctClass(),
fieldName= fieldName,
fieldClass= fieldEntry: get(0)
))
# add setter
this: _ctClass(): addMethod(generateSetter(
declaringClass= this: _ctClass(),
fieldName= fieldName,
fieldClass= fieldEntry: get(0)
))
}
})
this: _ctClass(): addInterface(resolveCtClass(java.io.Serializable.class))
this: methods(): filter(|methodName, entry| -> entry: get(3) is false): each(|methodName, entry| {
let counter = Observable(0)
let params = entry: get(1)?: map(|type| {
counter: set(counter: get() + 1)
return type: getName() + " arg" + counter: get()
})?: join(", ") orIfNull ""
let method = javassist.CtNewMethod.make(
"public " + (entry: get(0)?: getName() orIfNull "void") + " " + methodName +"("+ params +") { return null ; }"
, this: _ctClass()
)
this: _ctClass(): addMethod(method)
})
let klass = this: _ctClass(): toClass()
let klassDef = Adapter(): extends(this: className())
# if entry: get(3) is false -> implements, else overrides
this: methods(): filter(|methodName, entry| -> entry: get(3) is false): each(|methodName, entry| {
klassDef: implements(methodName, entry: get(2))
})
this: methods(): filter(|methodName, entry| -> entry: get(3) is true): each(|methodName, entry| {
klassDef: overrides(methodName, entry: get(2))
})
return klassDef
}
}
function DynamicClass = |className| {
let pool = javassist.ClassPool.getDefault()
let ctClass = pool: makeClass(className)
let dynaKlass = dynamicClass(className, map[], map[], ctClass)
return dynaKlass
}
# TODO:
# - default values (?)
# - constructor (?)
# - how to extend an existing class ?
function main = |args| {
let Human = DynamicClass("org.k33g.Human")
: field("name", String.class) # always public
: field("age", Integer.class)
: privateField("nickName", String.class) # if private then getter & setter are automatically generated
: overrides(
methodName = "setNickName",
typeReturn = null,
arguments = [String.class],
method = |super, this, value| {
println("overriding the setter")
super(this, value)
}
)
: overrides(
methodName = "getNickName",
typeReturn = String.class,
arguments = null,
method = |super, this| {
println("overriding the getter")
return super(this)
}
)
: implements(
methodName = "hello",
typeReturn = null,
arguments = null,
method = |this| -> println("Hello " + this: name() + "!")
)
: implements(
methodName = "yo",
typeReturn = null,
arguments = null,
method = |this| -> println(">>> " + this: age() + "!")
)
: implements(
methodName = "toString",
typeReturn = String.class,
arguments = null,
method = |this| -> this: name() + " - " + this: age()
)
: implements(
methodName = "changeAge",
typeReturn = null,
arguments = [Integer.class],
method = |this, newAge| -> this: age(newAge)
)
: implements(
methodName = "say",
typeReturn = String.class,
arguments = [String.class, String.class],
method = |this, message, someBody| {
return "Hey " + someBody + "! I'am " + this: name() + ", " + message
}
)
: toAdapter()
let bob = Human: newInstance()
bob: name("Bob Morane"): age(42)
bob: hello()
bob: yo()
bob: changeAge(24)
println(bob: toString())
println(bob: say(message="I'm happy", someBody="Sam"))
bob: setNickName("bobby")
println(bob: getNickName())
#let objDef = Adapter(): extends("org.k33g.Human")
#let sam = objDef: newInstance()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment