Как связать все методы определенного имени в объекте с шаблоном через карту привязки?

Обычным действием в шаблонах Groovy является привязка именованного объекта к области действия шаблона следующим образом:

map.put("someObject",object)
template.bind(map)

Затем в шаблоне я могу ссылаться и использовать someObject следующим образом:

someObject.helloWorld()
someObject.helloWorld("Hi Everyone!")
someObject.helloWorld("Hi", "Everyone!")

Внутри шаблона Groovy также позволяет мне определять дескриптор метода как первоклассную переменную в шаблоне следующим образом:

someObject = someObject.&helloWorld

Затем я могу сделать это, не обращаясь к имени объекта и метода:

 someObject() 
 someObject("Hello World")
 someObject("Hello", "World") 

Как я могу привязать ссылку на метод, подобную этой, на этапе 'template.bind (map)' вместе с автоматическим разрешением всех комбинаций параметров, таких как оператор '. &' В сценарии?

Использование MethodClosure не работает - вот простой тест и ошибка, которую я получаю

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();

Ошибка

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: []

Пользователь предлагает использовать просто ".call (..)"

"testMethod.call();" +
"testMethod.call(1);" +
"testMethod.call(1,2);" +

Но это противоречит цели. В этом случае я мог бы просто привязать объект вместо testMethod и использовать его обычно в шаблоне Groovy с обычными вызовами методов. Так что это не решение.

Решение создаст такую ​​привязку, чтобы testMethod () можно было вызывать точно так же и разрешать для всех перегруженных методов, как и предоставляет «mc1 = testInstance. & Test».

mc1 - это MethodClosure, а 'mc1 = testInstance. & test' творит чудеса, которые я хочу сделать на этапе привязки!

Метаклассом mc1 является HandleMetaClass. Я также могу настроить метакласс метода закрытия со стороны Java. Я просто хочу знать, как это сделать, чтобы добиться такого же поведения. Groovy делает это в шаблоне (со стороны Java в интерпретаторе шаблона), поэтому я хочу сделать то же самое заранее.

Обратите внимание, что обычно шаблон потоковой передачи уже создает своего собственного делегата. Когда код шаблона def mc1 = testInstance. интерпретируется, компилятор / интерпретатор Groovy использует этот делегат при создании MethodClosure с HandleMetaClass, а затем устанавливает его в существующий делегат.

Тогда правильный ответ не устанавливает заменяющего делегата согласно ответу @Dany ниже, вместо этого работает с существующим делегатом и создает правильные объекты для облегчения использования mc1 без Синтаксис .call.


person The Coordinator    schedule 17.12.2016    source источник
comment
Я полагаю, вы пробовали map.put("someObject",object.&helloWorld)   -  person tim_yates    schedule 17.12.2016
comment
@tim_yates Это работает только внутри скрипта. Я хочу выполнить привязку, как это уже указано на карте, предоставленной в template.bind (map). Т.е. Нужен способ сделать правильный объект Groovy внешним по отношению к сценарию.   -  person The Coordinator    schedule 18.12.2016
comment
У вас есть простой, работоспособный пример, показывающий, что вы пытаетесь?   -  person tim_yates    schedule 21.12.2016
comment
@tim_yates знакомы ли вы с созданием шаблонов с помощью Groovy?   -  person The Coordinator    schedule 21.12.2016
comment
Да, но я беспокоюсь, что любой пример, который я придумал, не будет соответствовать проблеме, которую вы видите ... Вот почему полный, простой, неудачный пример был бы крутым :-)   -  person tim_yates    schedule 21.12.2016
comment
def handle = someObject. & helloWorld создаст универсальный дескриптор метода и всех перегруженных подписей. Затем вы можете сделать handle (что угодно) вместо someObject.helloWorld (что угодно). Но я хочу сделать определение '. &' Со стороны Java перед вызовом шаблона. IE Не использовать метод def foo = * bar. & В шаблоне, а предварительно привязать foo. Дайте мне знать, если я все еще не ясно это описываю.   -  person The Coordinator    schedule 22.12.2016
comment
Есть ли mrhaki.blogspot.co.uk / 2011/11 / ближе?   -  person tim_yates    schedule 22.12.2016
comment
@tim_yates не имеет отношения. Вероятно, это потребует преобразования объекта в GroovyObject или Closure (Groovy), а затем также присвоения ему MethodClosure. Я просто не разбираюсь в этом с точки зрения Java.   -  person The Coordinator    schedule 23.12.2016


Ответы (4)


Вы можете добиться желаемого поведения, изменив ResolveStrategy на OWNER_FIRST < / а>. Поскольку вы хотите получить доступ к ограниченному замыканию напрямую (без нотации.), Вам необходимо привязать метод замыкания к «владельцу» (самому объекту шаблона) вместо предоставления через карту привязок (делегат).

Ваш измененный пример:

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();

Надеюсь, это соответствует вашим требованиям.

person skadya    schedule 13.02.2017
comment
Кажется, работает. Перейду к своим окончательным испытаниям. Кстати, в вашем примере также работает, если стратегия разрешения сбрасывается на DELEGATE_FIRST после вызова InvokerHelper.setProperty. - person The Coordinator; 14.02.2017
comment
Все тесты прошли. Смотрится бомбардировщик! Спасибо. - person The Coordinator; 14.02.2017

Это будет работать:

"<% " +
"def mc1=testInstance.&test;" +
"mc1();" +
"mc1('var1');" +
"mc1('var1', 'var2');" +
"testMethod.call();" +
"testMethod.call(1);" +
"testMethod.call(1,2);" +
" %>"

testMethod.call(...) работает, потому что переведено на getProperty('testMethod').invokeMethod('call', ...). Свойство testMethod определено в привязке и относится к типу MethodClosure, для которого определен метод call.

Однако testMethod(...) переведен на invokeMethod('testMethod', ...). Это не удается, потому что testMethod метод не определен, и вы не можете определить его через привязку.

ИЗМЕНИТЬ

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)
  }
}

Or 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);
    }
}
person Dany    schedule 20.01.2017
comment
Позвольте нам продолжить это обсуждение в чате. - person Dany; 20.01.2017

someObject.&helloWorld - это экземпляр MethodClosure, и код переводится в new MethodClosure(someObject, "helloWorld") (MetodClosure взят из пакета org.codehaus.groovy.runtime). Таким образом вы можете подготовить карту на Java.

person blackdrag    schedule 18.01.2017
comment
Пробовал. Не сработало. Я опубликовал тестовый пример в исходном вопросе выше, показывающий ошибку. - person The Coordinator; 18.01.2017

Это выражение def, имеющее особую семантику. Добавление:

def testMethod = testMethod;

непосредственно перед вызовом testMethod () делает трюк в вашем примере. Я не думаю, что с помощью одних привязок можно добиться того же результата.

РЕДАКТИРОВАТЬ: чтобы уточнить, поскольку объект testMethod никоим образом не изменяется при оценке выражения def, вероятно, вы не можете построить testMethod таким образом, чтобы он автоматически регистрировался как метод, предоставляя его как привязку. Возможно, вам повезет больше, если вы поиграете с метаклассом шаблона.

Writable templateImpl = t.make(bindings);
MetaClass metaClass = ((Closure) templateImpl).getMetaClass();
person Alessio Stalla    schedule 13.02.2017
comment
Спасибо, что пришли сюда. См. Ответ скадьи выше. - person The Coordinator; 15.02.2017