java - ¿Cómo puedo vincular todos los métodos de un determinado nombre en un objeto en una plantilla a través del mapa de enlace?
groovy binding (4)
Una acción normal en las plantillas Groovy es vincular un objeto con nombre en el ámbito de la plantilla de esta manera:
map.put("someObject",object)
template.bind(map)
Luego, en la plantilla puedo hacer referencia y usar ''someObject'' de esta manera:
someObject.helloWorld()
someObject.helloWorld("Hi Everyone!")
someObject.helloWorld("Hi", "Everyone!")
Dentro de la plantilla, Groovy también me permite definir un controlador de método como una variable de primera clase en la plantilla como esta:
someObject = someObject.&helloWorld
Luego, puedo hacer esto sin referirme al objeto y al nombre del método:
someObject()
someObject("Hello World")
someObject("Hello", "World")
¿Cómo puedo enlazar la referencia de método como esta en la etapa ''template.bind (map)'' junto con la resolución automática de todas las combinaciones de parámetros como el operador ''. &'' En el script que proporciona?
Usar un MethodClosure no funciona. Aquí hay una prueba simple y el error que recibo
class TestMethodClass {
public void test() {
System.out.println("test()");
}
public void test(Object arg) {
System.out.println("test( " + arg + " )");
}
public void test(Object arg1, Object arg2) {
System.out.println("test( " + arg1 + ", " + arg2 + " )");
}
}
String basic = "<%" +
" def mc1=testInstance.&test;" +
"println /"mc1 class ${mc1.getClass()}/";" +
"println /"mc1 metaclass ${mc1.getMetaClass()}/";" +
"println mc1.getClass();" +
"mc1();" +
"mc1(''var1'');" +
"mc1(''var1'', ''var2'');" +
"testMethod();" +
" %>";
Map<Object, Object> bindings = new HashMap<>();
bindings.put("testInstance", new TestMethodClass());
bindings.put("testMethod", new MethodClosure(new TestMethodClass(), "test"));
TemplateEngine engine = new GStringTemplateEngine();
Template t = engine.createTemplate(basic);
String result = t.make(bindings).toString();
Error
mc1 class class org.codehaus.groovy.runtime.MethodClosure
mc1 metaclass org.codehaus.groovy.runtime.HandleMetaClass@6069db50[groovy.lang.MetaClassImpl@6069db50[class org.codehaus.groovy.runtime.MethodClosure]]
class org.codehaus.groovy.runtime.MethodClosure
test()
test( var1 )
test( var1, var2 )
groovy.lang.MissingMethodException: No signature of method: groovy.lang.Binding.testMethod() is applicable for argument types: () values: []
Un usuario sugiere que solo use ''.call (..)''
"testMethod.call();" +
"testMethod.call(1);" +
"testMethod.call(1,2);" +
Pero eso derrota el propósito. En ese caso, también podría enlazar el objeto en lugar de ''testMethod'' y usarlo normalmente en la plantilla Groovy con llamadas de método regulares. Así que esa no es la solución aquí.
La solución creará un enlace tal que testMethod () puede llamarse así, y se resuelve para todos los métodos sobrecargados, al igual que la mc1 = testInstance. & Test "proporciona.
¡El mc1 es un MethodClosure y ''mc1 = testInstance. & Test'' hace algo de magia y quiero hacer esa magia en la etapa de enlace!
La metaclase de mc1 es un ''HandleMetaClass''. También puedo configurar de forma personalizada la metaclase del methodclosure desde el lado de Java. Solo quiero saber cómo hacerlo para obtener el mismo comportamiento. Groovy está haciendo eso en la plantilla (desde el lado de Java en el intérprete de la plantilla) y por eso quiero hacerlo de la misma manera, de antemano.
Tenga en cuenta que normalmente, la plantilla de transmisión ya crea su propio delegado. Cuando el código de plantilla ''def mc1 = testInstance. & Test;'' se interpreta, el compilador / intérprete Groovy utiliza ese delegado al crear el MethodClosure con un HandleMetaClass y luego lo instala en el delegado existente.
La respuesta correcta no instala un delegado de reemplazo según @Dany answer a continuación, sino que trabaja con el delegado existente y crea los objetos correctos para facilitar el uso de mc1 sin la sintaxis ''.call''.
Es la expresión ''def'' que tiene una semántica especial. Añadiendo:
def testMethod = testMethod;
Justo antes de invocar testMethod () hace el truco en tu ejemplo. No creo que puedas lograr el mismo resultado solo con los enlaces.
EDIT: para aclarar, ya que el objeto testMethod no se modifica de ninguna manera cuando se evalúa la expresión de definición, probablemente no pueda construir testMethod de tal manera que se registre automáticamente como un método proporcionándolo como un enlace. Podría tener más suerte jugando con la metaclase de la plantilla.
Writable templateImpl = t.make(bindings);
MetaClass metaClass = ((Closure) templateImpl).getMetaClass();
Esto funcionará:
"<% " +
"def mc1=testInstance.&test;" +
"mc1();" +
"mc1(''var1'');" +
"mc1(''var1'', ''var2'');" +
"testMethod.call();" +
"testMethod.call(1);" +
"testMethod.call(1,2);" +
" %>"
testMethod.call(...)
funciona porque está traducido a getProperty(''testMethod'').invokeMethod(''call'', ...)
. La propiedad testMethod
se define en el enlace y es del tipo MethodClosure
que tiene definido el método de call
.
Sin embargo, testMethod(...)
se traduce a invokeMethod(''testMethod'', ...)
. Falla porque no hay testMethod
método testMethod
definido y no puede definirse a través de un enlace.
EDITAR
Main.java
:
public class Main {
public static void main(String[] args) throws Throwable {
String basic = "<%" +
" def mc1=testInstance.&test;" +
"mc1();" +
"mc1(''var1'');" +
"mc1(''var1'', ''var2'');" +
"testMethod();" +
"testMethod(1);" +
"testMethod(2,3);" +
" %>";
Map<Object, Object> bindings = new HashMap<>();
bindings.put("testInstance", new TestMethodClass());
bindings.put("testMethod", new MethodClosure(new TestMethodClass(), "test"));
TemplateEngine engine = new GStringTemplateEngine();
Template t = engine.createTemplate(basic);
Closure make = (Closure) t.make();
make.setDelegate(new MyDelegate(bindings));
String result = make.toString();
}
}
MyDelegate.groovy
:
class MyDelegate extends Binding {
MyDelegate(Map binding) {
super(binding)
}
def invokeMethod(String name, Object args) {
getProperty(name).call(args)
}
}
O MyDelegate.java
:
public class MyDelegate extends Binding{
public MyDelegate(Map binding) {
super(binding);
}
public Object invokeMethod(String name, Object args) {
return DefaultGroovyMethods.invokeMethod(getProperty(name), "call", args);
}
}
Puede lograr el comportamiento deseado cambiando la ResolveStrategy de OWNER_FIRST a OWNER_FIRST . Como desea acceder directamente al cierre acotado (sin notación), debe vincular el método de cierre al "propietario" (objeto de plantilla en sí) en lugar de hacerlo a través del mapa de enlaces (delegado).
Su ejemplo modificado:
String basic = "<%" +
" def mc1=testInstance.&test;" +
"println /"mc1 class ${mc1.getClass()}/";" +
"println /"mc1 metaclass ${mc1.getMetaClass()}/";" +
"println mc1.getClass();" +
"mc1();" +
"mc1(''var1'');" +
"mc1(''var1'', ''var2'');" +
"testMethod();" +
"testMethod(''var1'');" +
" %>";
TemplateEngine engine = new GStringTemplateEngine();
TestMethodClass instance = new TestMethodClass();
// Prepare binding map
Map<String, Object> bindings = new HashMap<>();
bindings.put("testInstance", instance);
Template t = engine.createTemplate(basic);
Closure<?> make = (Closure<?>) t.make(bindings); // cast as closure
int resolveStrategy = make.getResolveStrategy();
make.setResolveStrategy(Closure.OWNER_FIRST);
// set method closure which you want to invoke directly (without .
// notation). This is more or less same what you pass via binding map
// but too verbose. You can encapsulate this inside a nice static
// method
InvokerHelper.setProperty(
make.getOwner(), "testMethod", new MethodClosure(instance, "test")
);
make.setResolveStrategy(resolveStrategy);
String result = make.toString();
Esperando, esto cumpla con sus requerimientos.
someObject.&helloWorld
es una instancia de MethodClosure y el código se traduce en el new MethodClosure(someObject, "helloWorld")
(MetodClosure es del paquete org.codehaus.groovy.runtime). De esta manera podrás preparar el mapa en Java.