groovy-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Marc Paquette <mar...@mac.com>
Subject Re: Reloading of script with GroovyClassLoader
Date Sun, 15 Nov 2015 22:32:14 GMT
Answering my own question in case it helps others.

My ultimate goal is to be able to execute DSL scripts at run-time from the filesystem, supporting
reloading, import customizers and a base script class.

First, `GroovyShell` does not do caching, so it always re-parse the scripts.  `GroovyClassLoader`
supports reloading, but with `loadClass` methods, not `parseClass` (found it by inspecting
the source code).  But `loadClass` is for classes, not for scripts (it is possible to use
it with scripts, but much more complicated).  `GroovyScriptEngine` is more suited to doing
this with scripts.  To support reloading, its `CompilerConfiguration` must have `recompileGroovySource`
set to true.  Since it has no constructor taking a compiler configuration but exposes the
`config` through getters/setters, one can get reloading by setting the compiler configuration
after instance creation of `GroovyScriptEngine`.  However, when a script is actually executed,
the compiler configuration in effect is not inherited from our instance of `GroovyScriptEngine`,
thus customization of the `scriptBaseClass` or import customizers added to the `compilationCustomizers`
are not available for the script.

The solution, as found while looking at `groovy.util.GroovyScriptEngineReloadingTest#testCompilerConfigurationInheritance`
in groovy sources, is to first create a `GroovyClassLoader` with our `CompileConfiguration`
properly setup and use that as the parent class loader when instantiating `GroovyScriptEngine`.
 This way, the customized compiler configuration is used when checking if a script should
be reloaded and when running scripts.

Here is a script showing all of this.  Note the `Thread.sleep(1000)` statements : on my system,
file modification time has a granularity of a second, so each change to the script file needs
to be at least one second after the last one or the previous compiled version of the script
will be used.

----
import org.codehaus.groovy.control.CompilerConfiguration

class MyGse {

    static abstract class MyBaseScript extends Script {
        String hello() { return 'Je suis Paris' }
    }

    public static void main(String[] args) {

        CompilerConfiguration config = new CompilerConfiguration()
        config.setRecompileGroovySource(true)
        config.scriptBaseClass = MyBaseScript.name
        GroovyScriptEngine gse = new GroovyScriptEngine(".", new GroovyClassLoader(getClassLoader(),
config))
        Binding binding = new Binding(alpha:'abc', num:'123')
        File fooFile = new File("foo.groovy")

        fooFile.text = 'alpha'
        def result = gse.run(fooFile.name, binding)
        assert 'abc' == result

        Thread.sleep(1000)
        fooFile.text = 'num'
        result = gse.run(fooFile.name, binding)
        assert '123' == result

        Thread.sleep(1000)
        fooFile.text = 'hello()'
        assert 'Je suis Paris' == gse.run(fooFile.name, binding)
        Thread.sleep(1000)
    }
}
----

Drop this in a file named 'MyGse.groovy', cd to its parent directory and run it with `groovy
MyGse.groovy`.
  
Hope this helps someone,
Marc


> Le 2015-11-14 à 10:31, Marc Paquette <marcpa@mac.com> a écrit :
> 
> Hello all,
> 
> I am using `GroovyClassLoader` to execute groovy scripts at runtime and I want modifications
to the script text (stored on the filesystem) to be picked up on subsequent execution of the
script but I don't want the script to be re-compiled each time.  From my understanding, creating
a `GroovyClassLoader` with a `CompilerConfiguration` where `recompileGroovySource` is set
to true should give this outcome, but it does not seems to work the way I am using it.
> 
> I expect the following script to pass, but the second assert fails, anybody can tell
me what I am doing wrong (or why my assumptions on how this is supposed to work are false)
?
> 
> 
> ----
> import org.codehaus.groovy.control.CompilerConfiguration
> import org.codehaus.groovy.runtime.InvokerHelper
> class MyGcl {
> 
>    public static void main(String[] args) {
>        CompilerConfiguration config
>        config = new CompilerConfiguration()
>        config.setRecompileGroovySource(true)
>        GroovyClassLoader gcl = new GroovyClassLoader(getClassLoader(), config)
>        Binding binding = new Binding()
> 
>        File fooFile = new File("foo.groovy")
>        fooFile.text = '''
> alpha='abc'
> num='123'
> alpha
> '''
>        def result = InvokerHelper.createScript(gcl.parseClass(fooFile), binding).run()
>        assert 'abc' == result
> 
>        fooFile.text = fooFile.text + '''
> num
> '''
>        result = InvokerHelper.createScript(gcl.parseClass(fooFile), binding).run()
>        assert '123' == result
>    }
> }
> ----
> 
> Thanks in advance,
> Marc
> 


Mime
View raw message