Didier
Didier

Reputation: 35

How to use function defined in a groovy script using the "evaluate" method from Java

I just discover Groovy call from Java and have problem with this case :

I have a groovy file : "test.groovy"

a = 1.0
def mul2( x ) { 2.0 * x }

And I want to use it from Java code like this

GroovyShell gs = new GroovyShell();
gs.parse( new File( ".../test.groovy" ) ).run();

System.out.printf( "a = %s%n", gs.evaluate("a") ); // ok
System.out.printf( "mul2(a) = %s%n", gs.evaluate( "mul2(a)" ) ); // error

The error is :

groovy.lang.MissingMethodException: No signature of method: Script1.mul2() is applicable for argument types: (BigDecimal) values: [1.0]

What I have to do to have access to function defined in groovy script, using evaluate() method ?

I need to use "evaluate" method because I want finally to evaluate something like Math.sin( a * mul2(Math.Pi) ).


Now I have 4 solutions (the forth is what I searched for) :

  1. use closure as in answer of 'Szymon Stepniak'
  2. use import static as in answer of 'daggett'
  3. extends the script that contains Java functions with the script that evaluate the expression :

...the class (in Java, not Groovy)...

  public static abstract class ScriptClass extends Script
  {
    double mul2( double x )
    {
      return x * 2;
    }
  }

...the code...

  CompilerConfiguration config = new CompilerConfiguration();
  config.setScriptBaseClass(ScriptClass.class.getName());

  GroovyShell gs = new GroovyShell(config);

  System.out.printf( "result = %s%n", gs.evaluate("mul2(5.05)") );

That works but the code is in Java, not what I want, but I note it here for ones need to do that

  1. And finally extends groovy script :

the groovy file :

double mul2( x ) { x * 2 } 
a=mul2(3.33)

the java code that use it

GroovyClassLoader gcl = new GroovyClassLoader();
Class<?> r = gcl.parseClass( resourceToFile("/testx.groovy") );
CompilerConfiguration config = new CompilerConfiguration();
config.setScriptBaseClass(r.getName());
GroovyShell gs = new GroovyShell(gcl, config);    

System.out.printf( "mul2(5.05) = %s%n", gs.evaluate("mul2(5.05)") );

// WARNING : call super.run() in evaluate expression to have access to variables defined in script
System.out.printf( "result = %s%n", gs.evaluate("super.run(); mul2(a) / 123.0") );

It's exactly what I wanted :-)

Upvotes: 3

Views: 4343

Answers (2)

Szymon Stepniak
Szymon Stepniak

Reputation: 42184

There are two things worth explaining to understand what is happening here. There are two different scopes in the script you have provided.

The variable a gets stored in GroovyShell binding object and that is why it is available in every gs.evaluate() call. Take a look at this example:

import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;

final class ExecuteGroovyScriptExample {

    public static void main(String[] args) {
        final String script = "a = 1.0 \n" +
                "def mul2(x) { 2.0 * x }\n";

        final Binding binding = new Binding();

        final GroovyShell gs = new GroovyShell(binding);
        final Script sc = gs.parse(script);
        sc.run();

        System.out.printf("binding.getVariable(\"a\") == %s\n", binding.getVariable("a"));
    }
}

Running this example produces following output:

binding.getVariable("a") == 1.0

The second thing is that every gs.evaluate() call generates a new groovy.lang.Script class which has a completely different context. This is why calling:

gs.evaluate("mul2(a)")

throws something like this:

Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: Script2.mul2() is applicable for argument types: (BigDecimal) values: [1.0]

because the script class that gets generated from gs.evaluate("mul2(a)") invocation does not contain mul2(x) method. The class that gets generated by this call looks something like this:

class Script2 extends groovy.lang.Script {
    void run() {
        mul2(a)
    }
}

However, the script class returned from gs.parse(script) contains mul2(x) method, so you can invoke it, but not as gs.evaluate() call, but Script.invokeMethod(name, args) instead. Something like this:

import groovy.lang.GroovyShell;
import groovy.lang.Script;

final class ExecuteGroovyScriptExample {

    public static void main(String[] args) {
        final String script = "a = 1.0 \n" +
                "def mul2(x) { 2.0 * x }\n";

        final GroovyShell gs = new GroovyShell();
        final Script sc = gs.parse(script);
        sc.run();

        System.out.printf("mul2(a) = %s%n", sc.invokeMethod("mul2", gs.evaluate("a")));
    }
}

This example produces following output:

mul2(a) = 2.00

Take a look how mul2(x) method got invoked. Firstly, we store script returned by gs.parse(script) in sc variable and it allows us to invoke method defined in this script by following call:

sc.invokeMethod("mul2", gs.evaluate("a"));

In this example we take value of a variable simply by gs.evaluate("a"), but you can also use binding object from the first example as well. And keep in mind that if a variable was defined like:

def a = 1.0

or

@groovy.transform.Field
def a = 1.0

it would not get stored in the binding object anymore and in the first case it defines script's local variable a and in the second case it defines script class field a.


Alternatively, if you want to execute following invocation:

gs.evaluate("mul2(a)")

or even

gs.evaluate("Math.sin( a * mul2(Math.PI))")

you would have to modify input Groovy script file and replace function mul2(x) definition with a closure in the same scope as the a variable, so it gets stored in the binding object:

a = 1.0
mul2 = { x -> 2.0 * x }

Upvotes: 4

daggett
daggett

Reputation: 28564

let you have /my/groovy/classes/Test.groovy:

static mul2( x ) { 2.0 * x }
def    mul3( x ) { 3.0 * x }

then you can use class loader to load it as a class and use this class in expressions:

GroovyShell gs = new GroovyShell();
gs.getClassLoader().addClasspath("/my/groovy/classes");

System.out.println( gs.evaluate( "import static Test.*; mul2(5)" ) );
System.out.println( gs.evaluate( "new Test().mul3(6)" ) );

Upvotes: 0

Related Questions