Kannan
Kannan

Reputation: 21

Groovy: How do I evaluate expression which has functions inside Java code?

Here's my sample expression:

foo("hello") + bar("world")

I am using groovy (2.0.5) as an expression parser so don't want to define functions (foo and bar) in groovy but would rather like to get notified as Java calls when the expression is parsed. I suppose this can be done by using one of the flavors of meta class. Unfortunately I tried all combinations but my implementation of meta class (i.e., MyExpandoMetaClass's invokeMissingMethod) never get called.

Any help is appreciated.

Sample Java Code:

MetaClassRegistryImpl.getInstance(0).setMetaClass(Script.class, new MyExpandoMetaClass());

String expression = "foo(\"hello\") + bar (\"world\")";
Binding binding = new Binding();
Script script = new GroovyShell(binding).parse(expression);
assertEquals("helloworld", script.evaluate(expression));

...

class MyExpandoMetaClass extends ExpandoMetaClass {
    public MyExpandoMetaClass() {
        super(Script.class, true);
    }

    @Override
    public Object invokeMissingMethod(Object instance, String methodName, Object[] arguments) {
        if (("foo".equals(methodName) || "bar".equals(methodName)) {
            return arguments[0];
        }
    }
}

When I run this code, I get:

groovy.lang.MissingMethodException: No signature of method: Script1.foo() is applicable for argument types: (java.lang.String) values: [hello]
Possible solutions: run(), run(), find(), any(), is(java.lang.Object), find(groovy.lang.Closure)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:55)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:78)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
    at Script1.run(Script1.groovy:1)
    at groovy.lang.GroovyShell.evaluate(GroovyShell.java:518)
    at groovy.lang.GroovyShell.evaluate(GroovyShell.java:556)
    at groovy.lang.GroovyShell.evaluate(GroovyShell.java:527)
    at groovy.lang.Script.evaluate(Script.java:208)
    at com.barcap.edt.amd.analytics.operation.postprocess.ExpressionParserTest.function(ExpressionParserTest.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Upvotes: 2

Views: 2006

Answers (2)

Leisure Dong
Leisure Dong

Reputation: 77

The complete Java solution of Micheal's answer is following:

1.Write Groovy script with Java(the same as Micheal's answer):

import groovy.lang.Script;

public class FooBarScript extends Script {
    public String foo(String x) {
        System.out.println("hello from foo");
        return x;
    }

    public String bar(String x) {
        System.out.println("hello from bar");
        return x;
    }

    public Object run() { return null; }
}

2.Call the methods defined in FooBarScript.java:

import groovy.lang.GroovyShell;
import groovy.lang.Script;
import org.codehaus.groovy.control.CompilerConfiguration;

public class Main {
    private static final GroovyShell shell;

    static {
        CompilerConfiguration cfg = new CompilerConfiguration();
        cfg.setScriptBaseClass(FooBarScript.class.getName());
        shell = new GroovyShell(cfg);
    }

    public static void main(String[] args) {
        String expression = "println(foo('hello') + bar ('world'))";
        Script script = shell.parse(expression);
        script.run();
    }
}

The output:

hello from foo
hello from bar
helloworld

Upvotes: 0

Michael Easter
Michael Easter

Reputation: 24498

This post is an example of using GroovyShell with a CompilerConfiguration, which may be useful.

Using Groovy 2.0.5, this code:

import org.codehaus.groovy.control.CompilerConfiguration

abstract class FooBarScript extends Script {
    def foo(def x) { 
        println "hello from foo"
        return x 
    }

    def bar(def x) { 
        println "hello from bar" 
        return x 
    }
}

def compilerConfiguration = new CompilerConfiguration()
compilerConfiguration.scriptBaseClass = FooBarScript.class.name

def binding = new Binding()

def shell = new GroovyShell(this.class.classLoader, binding, compilerConfiguration)

String expression = "foo(\"hello\") + bar (\"world\")"

def result = shell.evaluate expression
println result

gives this output:

hello from foo
hello from bar
helloworld

Given that the original post requested Java, the script can be moved to the following Java class:

import groovy.lang.Script;

public class FooBarScript extends Script {
    public String foo(String x) {
        System.out.println("hello from foo");
        return x;
    }

    public String bar(String x) {
        System.out.println("hello from bar");
        return x;
    }

    public Object run() { return null; }
}

where this can simply be imported into the Groovy example

Upvotes: 2

Related Questions