Meta-programming

Toda classe em Groovy implementa a interface GroovyObject:

// GroovyObject.java
public interface GroovyObject {
   public Object invokeMethod(String name, Object args);
   public Object getProperty(String property);
   public void setProperty(String property, Object newValue);
   public MetaClass getMetaClass();
   public void setMetaClass(MetaClass metaClass);
}
// script.groovy
class MyClass {
}
assert GroovyObject.isAssignableFrom(MyClass.class)

Essa a base para a meta-programação (meta-programming) que acontece em tempo de execução.

Quando a classe Groovy não implementa, explicitamente, métodos como invokeMethod ou getProperty, implementações padrões são usadas. As implementações padrões de invokeMethod e get/setProperty delegam todas as chamadas de métodos para a instância de MetaClass retornada por GroovyObject.getMetaClass(). A implementação padrão desse método, por sua vez, obtém a instância de MetaClass a partir do MetaClassRegistry. A MetaClass obtida é usada para determinar quais são os métodos e as propriedades disponíveis em cada classe e é ela quem realizará a invocação “real” desses métodos ou o acesso às propriedades.

Visualizando o quadro completo, teremos que, para cada invocação de método em seu código Groovy,

'My text'.doSomethingUnusual()
myObj.run()

será gerado bytecode para uma chamada ao ScriptBytecodeAdapter (por exemplo, para o método invokeMethodN)

public static Object invokeMethodN(Class senderClass, Object receiver, 
                                  String messageName, 
                                  Object[] messageArguments) throws Throwable {
        try {    
            return InvokerHelper.invokeMethod(receiver, messageName, 
                                                        messageArguments);
        } catch (GroovyRuntimeException gre) {
            throw unwrap(gre);
        }
    }

e o InvokerHelper decidirá como será obtida a MetaClass para cada caso

    /**
     * Invokes the given method on the object.
     */
    public static Object invokeMethod(Object object, String methodName, 
                                               Object arguments) {
        if (object == null) {
            object = NullObject.getNullObject();
            //throw new NullPointerException("Cannot invoke method " + methodName + "() on null object");
        }

        // if the object is a Class, call a static method from that class
        if (object instanceof Class) {
            Class theClass = (Class) object;
            MetaClass metaClass = metaRegistry.getMetaClass(theClass);
            return metaClass.invokeStaticMethod(object, methodName, 
                                                           asArray(arguments));
        }

        // it's an instance; check if it's a Java one
        if (!(object instanceof GroovyObject)) {
            return invokePojoMethod(object, methodName, arguments);
        }

        // a groovy instance (including builder, closure, ...)
        return invokePogoMethod(object, methodName, arguments);
    }
    static Object invokePojoMethod(Object object, String methodName, Object arguments) {
        MetaClass metaClass = InvokerHelper.getMetaClass(object);
        return metaClass.invokeMethod(object, methodName, asArray(arguments));
    }

    static Object invokePogoMethod(Object object, String methodName, Object arguments) {
        GroovyObject groovy = (GroovyObject) object;
        boolean intercepting = groovy instanceof GroovyInterceptable;
        try {
            // if it's a pure interceptable object (even intercepting toString(), clone(), ...)
            if (intercepting) {
                return groovy.invokeMethod(methodName, asUnwrappedArray(arguments));
            }
            //else try a statically typed method or a GDK method
            return groovy.getMetaClass().invokeMethod(object, methodName, asArray(arguments));
        } catch (MissingMethodException e) {
            if (e instanceof MissingMethodExecutionFailed) {
                throw (MissingMethodException) e.getCause();
            } else if (!intercepting && e.getMethod().equals(methodName) && object.getClass() == e.getType()) {
                // in case there's nothing else, invoke the object's own invokeMethod()
                return groovy.invokeMethod(methodName, asUnwrappedArray(arguments));
            } else {
                throw e;
            }
        }
    }

Agora vejamos alguns exemplos do que pode ser realizado, em termos de meta-programação, através da implementação da interface GroovyObject:

  • Responder, dinamicamente, à invocação de métodos:

    class MyClass {
    
        public void method1() {
             println 'method1()'
        }
    
        @Override
        public Object invokeMethod(String name, Object args) {
             println "invokeMethod ${name}(${args})"
        }
    
    }
    
    MyClass c = new MyClass()
    c.method1()
    c.method2()
    c.method3(12, false)
    
  • Responder, dinamicamente, à invocação de métodos e interceptar as chamadas aos métodos existentes:

    class MyClass implements GroovyInterceptable {
    
        public String method1() {
             return 'method1 result'
        }
    
        @Override
        public Object invokeMethod(String name, Object args) {
             MetaClass metaClass = this.metaClass
             MetaMethod metaMethod = null
    
             if (metaClass.respondsTo(this, name)) {
                 metaMethod = metaClass.getMetaMethod(name)
                 return "intercepting ${metaMethod.doMethodInvoke(this, args)}"
             } else {
                return "dynamic method ${name}(${args})"
             }                  
        }
    
    }
    
    MyClass c = new MyClass()
    println c.method1()
    println c.method2()
    println c.method3(12, false)
    
  • Criar Builders simples:

    class HtmlBuilder {
       def out
    
       def invokeMethod(String name, args) {
           if (args) {
              int idx = 0
              if (args[idx] instanceof Map) {
                Map atts = args[idx]
                out << "<${name} "
                atts.entrySet().each{out << "${it.key}='${it.value}'"}
                out << ">"
                idx++
              } else {
                out << "<$name>"
              }
    
              if (idx < args.length) {
                 if(args[idx] instanceof Closure) {
                    args[idx].delegate = this
                   args[idx].call()
                } else {
                  out << args[idx].toString()
                }
              }
    
              out << "</$name>"
           } else {
              out << "<$name />"
           }
    
       }
    }
    
    def html = new HtmlBuilder(out: new StringWriter())
    html.html {
       head {
          meta(name: 'keywords', content: 'HTML, CSS, XML')
       }
       body(onload: 'javascript: return false;') {
           p 'This is a page'
       }
    }
    

ExpandoMetaClass

Groovy inclui uma MetaClass especial chamada ExpandoMetaClass. Essa MetaClass permite que construtores, métodos e propriedades sejam facilmente adicionados a (ou sobreescritos em – overriding) qualquer java.lang.Class, em tempo de execução.

Por exemplo, para adicionar um método:

String.class.metaClass.reverse << {-> 
   delegate[delegate.length()-1..0]
}
println ('Using ExpandoMetaClass'.reverse())

Ou para sobreescrever um método já existente:

println 'Groovy'.endsWith('y ')
def oldMethod = String.class.metaClass.getMetaMethod('endsWith')
String.class.metaClass.endsWith = {String text -> 
   oldMethod.doMethodInvoke(delegate, text.trim())
}
println 'Groovy'.endsWith('y ')

Referências adicionais

Deixe uma resposta

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*


cinco + 6 =

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>