Created
August 26, 2015 17:31
-
-
Save k33g/1ff1fe6a228964f9437e to your computer and use it in GitHub Desktop.
dynamicClass.golo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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