javagroovygroovyscriptengine

Append CompiledScript or add two CompiledScript


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?


Solution

  • 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.