程式扎記: [ Groovy Doc ] ExpandoMetaClass - Dynamic method/property name creation (4)

標籤

2015年1月12日 星期一

[ Groovy Doc ] ExpandoMetaClass - Dynamic method/property name creation (4)

Source From Here 
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: 
  1. class Person {  
  2.    String name = "Fred"  
  3. }  
  4.   
  5. def methodNames = ["Bob""John"]  
  6.   
  7. for(name in methodNames)  
  8. {  
  9.     // Dynamically adding Method:changeNameToBob to Person class  
  10.     Person.metaClass."changeNameTo${name}" = {-> delegate.name = name }  
  11.       
  12.     def p = new Person()  
  13.     printf("Change name from %s", p.name)         
  14.     p."changeNameTo${name}"()  
  15.     printf(" to %s\n", p.name)  
  16. }  
Execution result: 
Change name from Fred to Bob
Change name from Fred to John

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: 
  1. import org.springframework.web.util.HtmlUtils  
  2. class HTMLCodec {  
  3.     static encode = { theTarget ->  
  4.         HtmlUtils.htmlEscape(theTarget.toString())  
  5.     }  
  6.   
  7.     static decode = { theTarget ->  
  8.         HtmlUtils.htmlUnescape(theTarget.toString())  
  9.     }  
  10. }  
So what we do with these classes is to evaluate the convention and add "encodeAsXXX" methods to class MyCls based on the first part of the name of the codec class such as "encodeAsHTML". The pseudo code to achieve this is below: 
  1. package test  
  2.   
  3. /** 
  4. * Scans all classes accessible from the context class loader which belong to the given package and subpackages. 
  5. * 
  6. * @param packageName The base package 
  7. * @return The classes 
  8. * @throws ClassNotFoundException 
  9. * @throws IOException 
  10. */  
  11. private static ArrayList getClasses(String packageName)  
  12.         throws ClassNotFoundException, IOException {  
  13.     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();  
  14.     assert classLoader != null;  
  15.     String path = packageName.replace('.''/');  
  16.     Enumeration resources = classLoader.getResources(path);  
  17.     List dirs = new ArrayList();  
  18.     while (resources.hasMoreElements()) {  
  19.         URL resource = resources.nextElement();  
  20.         dirs.add(new File(resource.getFile()));  
  21.     }  
  22.     ArrayList classes = new ArrayList();  
  23.     for (File directory : dirs) {  
  24.         classes.addAll(findClasses(directory, packageName));  
  25.     }  
  26.     return classes  
  27. }  
  28.   
  29. /** 
  30. * Recursive method used to find all classes in a given directory and subdirs. 
  31. * 
  32. * @param directory   The base directory 
  33. * @param packageName The package name for classes found inside the base directory 
  34. * @return The classes 
  35. * @throws ClassNotFoundException 
  36. */  
  37. private static List findClasses(File directory, String packageName) throws ClassNotFoundException {  
  38.     List classes = new ArrayList();  
  39.     if (!directory.exists()) {  
  40.         return classes;  
  41.     }  
  42.     File[] files = directory.listFiles();  
  43.     for (File file : files) {  
  44.         if (file.isDirectory()) {  
  45.             assert !file.getName().contains(".");  
  46.             classes.addAll(findClasses(file, packageName + "." + file.getName()));  
  47.         } else if (file.getName().endsWith(".class")) {  
  48.             classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));  
  49.         }  
  50.     }  
  51.     return classes;  
  52. }  
  53.     
  54. class HTMLCodec {    
  55.     static encode = { theTarget ->    
  56.        printf("HTMLEncode: %s\n", theTarget)    
  57.     }    
  58.     
  59.     static decode = { theTarget ->    
  60.        printf("HTMLDecode: %s\n", theTarget)    
  61.     }    
  62. }   
  63.   
  64. class JohnCodec {  
  65.     static encode = { theTarget ->  
  66.         printf("JohnEncode: %s\n", theTarget)  
  67.     }  
  68.      
  69.     static decode = { theTarget ->  
  70.         printf("JohnDecode: %s\n", theTarget)  
  71.     }  
  72. }  
  73.   
  74.   
  75. def clzs = getClasses("test")   
  76.   
  77.   
  78. //def codecs = cl.getLoadedClasses().findAll(it.name.endsWith('Codec'))  
  79. def codecs = clzs.findAll { it.name.endsWith('Codec') }  
  80.   
  81. codecs.each { codec ->  
  82.     printf("\t[Info] Handle %s\n", codec.name)  
  83.     String cname = codec.name.split("\\.")[-1]-'Codec'  
  84.     MyClz.metaClass."encodeAs${cname}" = {codec.newInstance().encode(delegate) }  
  85.     MyClz.metaClass."decodeFrom${cname}" = {codec.newInstance().decode(delegate) }  
  86. }  
  87.   
  88. class MyClz  
  89. {  
  90.     def name  
  91.       
  92.     @Override  
  93.     public String toString(){return name}  
  94. }  
  95.   
  96. MyClz mc = new MyClz(name:"test")  
  97. mc.encodeAsJohn()  
  98. mc.decodeFromJohn()  
Execution Result: 
[Info] Handle test.HTMLCodec
[Info] Handle test.JohnCodec
JohnEncode: test
JohnDecode: test

Supplement 
Get All Classes Within A Package 
Strings and GString

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!