3

Hello I'm using Groovy engine to eval a certain script. The problem is that the execution time is a little bit big, so I want to separate my script into two scripts. The first one is static and can be compiled inside @PostConstruct method and the second is a variable and it depends on some parameters that the user chooses. This is what I want to do:

Bindings bindings;
CompiledScript scriptC;
String script1="Static Script";
String script2="Variable Script";
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("groovy");
scriptC = ((Compilable) engine).compile(script1); 
//Execute some instruction to generate 'script2'
scriptC += ((Compilable) engine).compile(script2);//Please note the += operator

or

Bindings bindings;
CompiledScript scriptC;
String script1="Static Script";
String script2="Variable Script";
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("groovy");
scriptC = ((Compilable) engine).compile(script1); 
//Execute some instruction to generate 'script2'
scriptC.append(((Compilable) engine).compile(script2));//Please note the append function

These two code does not work for sure, I used them just to illustrate what I'm attempting to do. Is it possible to combine two scripts into a single one?

Mehdi Bouzidi
  • 1,937
  • 3
  • 15
  • 31

1 Answers1

1

You can't combine like that two CompiledScript objects. If it comes to Groovy - when Groovy script gets compiled it creates a class that extends groovy.lang.Script with a few methods inside. In this case adding two scripts to each other means merging two Java classes.

Consider evaluating both scripts in separation as an alternative. Take a look at following example:

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

public class ScriptTest {

    public static void main(String[] args) throws ScriptException {
        final ScriptEngineManager manager = new ScriptEngineManager();
        final ScriptEngine engine = manager.getEngineByName("Groovy");

        final Bindings bindings = new SimpleBindings();
        bindings.put("a", 3);

        final String script1 = "println 'Running first script...'; def c = 2; def d = c + a; return d";
        final CompiledScript compiledScript1 = ((Compilable) engine).compile(script1);

        //Execute some instruction to generate 'script2'
        final String script2 = "println 'Running second script...';";
        final CompiledScript compiledScript2 = ((Compilable) engine).compile(script2);

        Integer returnedValue = (Integer) compiledScript1.eval(bindings);
        System.out.println("Returned value from the first script: " + returnedValue);

        if (returnedValue > 1) {
            compiledScript2.eval(bindings);
        }
    }
}

Creating CompiledScript object is one thing. Second thing is evaluation of those scripts. If you always evaluate script1 and script2 right away, then you simply need:

compiledScript1.eval(bindings);
compiledScript2.eval(bindings);

However script1 may return some value and based on that value you can decide if script2 should be evaluated. Let's say script1 returns an Integer and I will evaluate script2 only if returned value is greater than 1:

Integer returnedValue = (Integer) compiledScript1.eval(bindings);
if (returnedValue > 1) {
    compiledScript2.eval(bindings);
}

Alternatively you can base on bindings values after running script1. Let's say script1 modifies bindings a and b and set both to true (assuming they hold boolean type):

compiledScript1.eval(bindings);
if (bindings.get("a") && bindings.get("b")) {
    compiledScript2.eval(bindings);
}

Calling a function defined in script1 from script2

Let's say script1 is defined as follows:

import groovy.json.JsonOutput

def json(Map map) {
    return new JsonOutput().toJson(map)
}

println json([test: 1])

In case you have to call json(map) function inside script2 - you can do it. Take a look at this example:

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

public class ScriptTest {

    public static void main(String[] args) throws ScriptException {
        final ScriptEngineManager manager = new ScriptEngineManager();
        final ScriptEngine engine = manager.getEngineByName("Groovy");

        final Bindings bindings = new SimpleBindings();
        bindings.put("a", 3);

        final String script1 = "import groovy.json.JsonOutput\n" +
                "\n" +
                "def json(Map map) {\n" +
                "    return new JsonOutput().toJson(map)\n" +
                "}\n" +
                "\n" +
                "println json([test: 1])";

        final CompiledScript compiledScript1 = ((Compilable) engine).compile(script1);

        //Execute some instruction to generate 'script2'
        final String script2 = "println 'Running second script...'; println json([test: 2]);";
        final CompiledScript compiledScript2 = ((Compilable) engine).compile(script2);

        compiledScript1.eval(bindings);
        compiledScript2.eval(bindings);
    }
}

script2 is defined as:

println 'Running second script...';
println json([test: 2]);

When you run it you will see in the console:

{"test":1}
Running second script...
{"test":2}

The first two lines come from script1 while the last line is generated by script2. Keep in mind that both CompiledScript hold a reference to the same engine, so you can refer to functions defined in previously evaluated scripts.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • Thank you, I understood what you're trying to demonstrate, but concretely, this is what I have: in the first script I have a definition of some functions, however in the second script I have a call to one of the defined function above. – Mehdi Bouzidi Feb 18 '18 at 12:34
  • @MehdiBouzidi You can refer from `script2` to a function defined in `script1` - the only requirement is that `script1 ` has to be evaluated before `script2`. Take a look at updated example in the answer above. – Szymon Stepniak Feb 18 '18 at 12:56