Preface
Since Groovy allows you to use Strings as property names this in turns allows you to dynamically create method and property names at runtime.
The Basics
To create a method with a dynamic name simply use Groovy's feature of reference property names as strings. You can combine this with Groovy's string interpolation (Gstring) to create method and property names on the fly:
- class Person {
- String name = "Fred"
- }
- def methodNames = ["Bob", "John"]
- for(name in methodNames)
- {
- // Dynamically adding Method:changeNameToBob to Person class
- Person.metaClass."changeNameTo${name}" = {-> delegate.name = name }
- def p = new Person()
- printf("Change name from %s", p.name)
- p."changeNameTo${name}"()
- printf(" to %s\n", p.name)
- }
The same concept can be applied to static methods and properties.
A more elaborate example
In Grails we have a concept of dynamic codecs, classes that can encode and decode data. These classes are called HTMLCodec, JavaScriptCodec etc. an example of which can be seen below:
- import org.springframework.web.util.HtmlUtils
- class HTMLCodec {
- static encode = { theTarget ->
- HtmlUtils.htmlEscape(theTarget.toString())
- }
- static decode = { theTarget ->
- HtmlUtils.htmlUnescape(theTarget.toString())
- }
- }
- package test
- /**
- * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
- *
- * @param packageName The base package
- * @return The classes
- * @throws ClassNotFoundException
- * @throws IOException
- */
- private static ArrayList
getClasses(String packageName) - throws ClassNotFoundException, IOException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- assert classLoader != null;
- String path = packageName.replace('.', '/');
- Enumeration
resources = classLoader.getResources(path); - List
dirs = new ArrayList (); - while (resources.hasMoreElements()) {
- URL resource = resources.nextElement();
- dirs.add(new File(resource.getFile()));
- }
- ArrayList
classes = new ArrayList (); - for (File directory : dirs) {
- classes.addAll(findClasses(directory, packageName));
- }
- return classes
- }
- /**
- * Recursive method used to find all classes in a given directory and subdirs.
- *
- * @param directory The base directory
- * @param packageName The package name for classes found inside the base directory
- * @return The classes
- * @throws ClassNotFoundException
- */
- private static List
findClasses(File directory, String packageName) throws ClassNotFoundException { - List
classes = new ArrayList (); - if (!directory.exists()) {
- return classes;
- }
- File[] files = directory.listFiles();
- for (File file : files) {
- if (file.isDirectory()) {
- assert !file.getName().contains(".");
- classes.addAll(findClasses(file, packageName + "." + file.getName()));
- } else if (file.getName().endsWith(".class")) {
- classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
- }
- }
- return classes;
- }
- class HTMLCodec {
- static encode = { theTarget ->
- printf("HTMLEncode: %s\n", theTarget)
- }
- static decode = { theTarget ->
- printf("HTMLDecode: %s\n", theTarget)
- }
- }
- class JohnCodec {
- static encode = { theTarget ->
- printf("JohnEncode: %s\n", theTarget)
- }
- static decode = { theTarget ->
- printf("JohnDecode: %s\n", theTarget)
- }
- }
- def clzs = getClasses("test")
- //def codecs = cl.getLoadedClasses().findAll(it.name.endsWith('Codec'))
- def codecs = clzs.findAll { it.name.endsWith('Codec') }
- codecs.each { codec ->
- printf("\t[Info] Handle %s\n", codec.name)
- String cname = codec.name.split("\\.")[-1]-'Codec'
- MyClz.metaClass."encodeAs${cname}" = {codec.newInstance().encode(delegate) }
- MyClz.metaClass."decodeFrom${cname}" = {codec.newInstance().decode(delegate) }
- }
- class MyClz
- {
- def name
- @Override
- public String toString(){return name}
- }
- MyClz mc = new MyClz(name:"test")
- mc.encodeAsJohn()
- mc.decodeFromJohn()
Supplement
* Get All Classes Within A Package
* Strings and GString
沒有留言:
張貼留言