Parsing classes with GroovyShell - groovy

I have a groovy script that need to run a method inside a class inside an external groovy script.
I know how to run a method within an external groovy script:
new GroovyShell().parse( new File( 'foo.groovy' ) ).with {
method()
}
But what if the method is inside a class? I tried this but it gave me an error.
new GroovyShell().parse( new File( 'foo.groovy' ) ).with {
theclass.method()
}

You can use Java reflection to create new instance of a Class that is located in another script:
File sourceFile = new File("D:\\anoutherScript.groovy")
//here you have to update your classloader with external script
getClass().getClassLoader().addURL(sourceFile.toURI().toURL())
GroovyObject obj = Class.forName("ClassInAnotherObject").newInstance()
obj.doSth()
Script in your external file would be like that:
class ClassInAnotherObject{
def doSth(){
}
}
but there could be more classes in script file, also some more instructions and method call. Just like normal groovy script.

Related

groovy script binding does not seem to operate

though I like Groovy sometimes the documentation is ... well terse
I have a problem to pass a binding to a script.
here is the code :
def conf = new CompilerConfiguration()
Binding env = new Binding()
//....
env.setProperty( // some key and value set
//
conf.setScriptBaseClass(// name of class that is a Script
def shell = new GroovyShell(env,conf)
//isReader is the reader of the code// argz is an array of Strings
shell.run(isReader, scriptName, argz)
well, for one, the "scriptname" parameter in "run" is an obscure feature (any hint? -the reader does not have the notion of a file-)
then the Binding I get in my script instance is different from the one I created! (so unable to pass variables back and forth)
I need to invoke my script using my script base class and pass a Binding and parameters to it ... if there is another way that works I would be delighted! thanks!
edit ... trying to set up examples :
package abcgroovy
abstract class MyScript extends Script{
MyScript() {
super()
def _env = getBinding()
println _env
}
MyScript(Binding binding) {
super(binding)
def _env = binding
println _env
}
}
then the invoking code :
def conf = new CompilerConfiguration()
Binding env = new Binding()
println "ENV calling :$env"
conf.setScriptBaseClass("abcgroovy.MyScript")
def shell = new GroovyShell(env,conf)
Reader isReader = new StringReader("println 'hello world'")
// second argument: can't guess what to put
shell.run(isReader,'abcgroovy.MyScript',new String[0])
now the result of a run :
ENV calling :groovy.lang.Binding#67a20f67
groovy.lang.Binding#6a192cfe
hello world
From my understanding the second parameters (fileName) is the name of the file that is parsed. This has no relevance to any functionality, but is used for error reporting. So if you have an error in your provided script, the fileName will be used in the error message. Thus, when using a different signature where you provide a URI or a File that parameter cannot be specified.
In your example the Binding from the GroovyShell actually is used, which you can verify by printing the Binding in the actual script.
def conf = new CompilerConfiguration()
Binding env = new Binding()
println "ENV calling :$env"
conf.setScriptBaseClass("abcgroovy.MyScript")
def shell = new GroovyShell(env,conf)
Reader isReader = new StringReader('println "binding in script: ${getBinding()}"')
shell.run(isReader,'abcgroovy.MyScript',new String[0])
The reason your example outputs a different Binding is the way the script is initialized. First a new MyScript instance is created without passing the Binding, then the Binding from the GroovyShell is set using setBinding. The constructor with the Binding parameter is not used.
abstract class MyScript extends Script {
MyScript() {
super()
println "MyScript() with binding ${getBinding()}"
}
MyScript(Binding binding) {
super(binding)
// not invoked!
println "MyScript(${binding})"
}
public void setBinding(Binding binding) {
super.setBinding(binding)
println "setBinding to ${binding}"
}
}
Example output
ENV calling :groovy.lang.Binding#335eadca
MyScript() with binding groovy.lang.Binding#45018215
setBinding to groovy.lang.Binding#335eadca
binding in script: groovy.lang.Binding#335eadca
Note that the Binding is temporarily different in the MyScript() constructor only.

Adding a method from an external groovy script with GroovyShell()

I have a groovy script (source.groovy) that needs to call a method from another groovy script (external.groovy). The problem is external.groovy imports a library that does not exists so I get an error. Here is an example:
Source.groovy:
new GroovyShell().parse( new File( 'external.groovy' ) ).with {
method()
}
Here is external.groovy:
import com.foo.doesnotexsist
def method() {println "test"}
When I run Source.groovy I get an error because com.foo.doesnotexsist does not exist. I don't care that it does not exists because it does not effect the method() function. Is there a way I can call the method() function?
Maybe it is not the way we want to achieve that, but there is simple solution to remove unwanted imports:
def text = new File( 'external.groovy' ).findAll{!(it =~ /^\s*import/)}.join('\n')
new GroovyShell().parse( text ).with{method()}

opennlp.groovy has NullPointerException

I am trying to get the following code snippet from GitHub to work so that I can use OpenNLP tools in Groovy scripts.
(OpenNLP class from https://gist.github.com/nagaimasato/1178725)
#!/usr/bin/env groovy
#Grapes(
#Grab(
group='org.apache.opennlp',
module='opennlp-tools',
version='1.5.3'
)
)
import opennlp.tools.tokenize.*
import opennlp.tools.postag.*
OpenNLP nlp = new OpenNLP()
def tokens = nlp.workTokenize("Hello world")
println tokens
class OpenNLP {
static TokenizerModel tokenizerModel
static POSModel posModel
static {
def classLoader = OpenNLP.class.classLoader
classLoader.getResource('opennlp/en-token.bin').withInputStream {
tokenizerModel = new TokenizerModel(it)
}
classLoader.getResource('opennlp/en-pos-maxent.bin').withInputStream {
posModel = new POSModel(it)
}
}
Tokenizer tokenizer
POSTagger tagger
OpenNLP() {
tokenizer = new TokenizerME(tokenizerModel)
tagger = new POSTaggerME(posModel)
}
List workTokenize(String text) {
return tokenizer.tokenize(text)
}
List posTag(List tokens) {
return [tokens, tagger.tag(tokens)].transpose()
}
}
I get the following error when I try to run the script:
Caught: java.lang.ExceptionInInitializerError
java.lang.ExceptionInInitializerError
at Greetings.class$(Greetings.groovy)
at Greetings.$get$$class$OpenNLP(Greetings.groovy)
at Greetings.run(Greetings.groovy:13)
Caused by: java.lang.NullPointerException: Cannot invoke method withInputStream() on null object
at OpenNLP.<clinit>(Greetings.groovy:25)
... 3 more
I have en-token.bin and en-pos-maxent.bin in the right place for the script to find, but classLoader.getResource("opennlp/en-token.bin") is indeed null when I print it. Any ideas?
Make sure the en-token.bin and en-pos-maxent.bin files are in a directory named opennlp and that the classpath includes the parent directory of opennlp.
Note that ./ is included in the classpath when you execute your Groovy script, so if you have your opennlp directory in the same directory as your Groovy script and you also invoke the Groovy script while you are in that directory, it should work (at least it worked for me). But if you execute the script while you are not currently in that directory (e.g. by using a path like path/to/script.groovy) it won't work. In that case, you can just invoke it using groovy -cp path/to path/to/script.groovy, thus putting the parent directory of your opennlp directory into the classpath manually.

Groovyc successfully compiles invalid groovy scripts

c:\>cat invalid.groovy
com.test.build(binding)
c:\junk>groovyc invalid.groovy
c:\junk>ls invalid.class
invalid.class
Why does this not result in an compilation error? There is no such method com.test.build!
In what scenario will this compilation be successful?
As Groovy is dynamic it cannot know that there will be no com.test.build at runtime
com may be an object added to the binding at a later date in the code's execution
An example:
Say we have this bit of groovy inside Script.groovy
com.test.build( binding )
Compile it with groovyc:
groovyc Script.groovy
Then, keep the Script.class file, and throw away the groovy script to make sure we are using the class file:
rm Script.groovy
Now, say we have the following in Runner.groovy
// Create a class which you can call test.build( a ) on
class Example {
Map test = [
build : { it ->
println "build called with parameter $it"
}
]
}
// Create one of our Com objects
def variable = new Example()
// Load the Script.class, and make an instance of it
def otherscript = new GroovyClassLoader().loadClass( 'Script' ).newInstance()
// Then, set `com` in the scripts binding to be our variable from above
otherscript.binding.com = variable
// Then, run the script
otherscript.run()
That prints:
build called with parameter groovy.lang.Binding#7bf35647
So as you can see, it gets the test parameter of the com object (the map above) and calls the build closure with the binding as a parameter...
Hope this makes sense...

How do I set an embedded Groovy scripts classpath?

I am trying to extend an Eclipse code builder (for generating DTOs from Hibernate VOs) - and it uses Groovy for its template system.
The code it uses to create the groovy Script is a little weird (not what I see in the Groovy docs) but it works, mostly:
GroovyShell shell = new GroovyShell();
script = shell.parse(source);
Then, later:
Binding binding = (bindings == null ? new Binding() : new Binding(bindings));
Script scriptInstance = InvokerHelper.createScript(script.getClass(), binding);
scriptInstance.setProperty("out", out);
scriptInstance.run();
out.flush();
Now, this works just fine, until it hits a reference to an object that is not directly in the project. In the script, it iterates through the properties of the Class that it is processing - when it does this, Groovy looks at all of the methods and when it can't find a Class definition for one of the method parameters, it craps out. In this case, it's dying when it finds any references to Hibernate, but I'm sure it will crap out with a lot more. It doesn't need to do anything to them, but it can't live without knowing what they are apparently.
Script doesn't appear to have a classloader that I can supply any classpath info, so I tried providing it to the GroovyShell - no difference.
What's the proper way to fix this so that the Groovy interpreter can find my projects referenced Jars?
I had this exact problem and solved it by creating my own URLClassLoader, and using reflection to call a protected method to add a new path to the ClassPath
// Specify the path you want to add
URL url = new URL("file://path/to/classes/here");
// Create a new class loader as a child of the default system class loader
ClassLoader loader = new URLClassLoader(System.getClass().getClassLoader());
// Get the AddURL method and call it
Method method = URLClassLoader.class.getDeclaredMethod("addURL",new Class[]{URL.class});
method.setAccessible(true);
method.invoke(loader,new Object[]{ url });
GroovyShell shell = new GroovyShell( loader );
The same as #James can be done without using reflection, loading all jar files from a certain folder:
URLClassLoader classLoader = new URLClassLoader( getExtraJarUrls(), getClass().getClassLoader() );
GroovyShell shell = new GroovyShell( classLoader, binding, compilerConfiguration );
private URL[] getExtraJarUrls() throws MalformedURLException
{
logger.debug( "Loading extra jars from {}", EXTRA_JARS_DIR.getAbsolutePath() );
URL[] result;
File[] files = EXTRA_JARS_DIR.listFiles( new JarFilenameFilter() );
if (files != null)
{
List<URL> urls = new ArrayList<URL>( files.length );
for (File file : files)
{
urls.add( file.toURI().toURL() );
}
result = urls.toArray( new URL[urls.size()] );
}
else
{
result = new URL[0];
}
logger.debug( "Adding URLs to classloader: {}", Arrays.toString( result ) );
return result;
}
private static class JarFilenameFilter implements FilenameFilter
{
public boolean accept( File dir, String name )
{
return name.endsWith( ".jar" );
}
}
I'm having the same problem trying to automate Gant scripts running. The solution I found is:
copy gant-starter.conf (or
groovy-starter.conf if it's just
groovy) from $GROOVY_HOME/conf to your
own dir;
add "load [directory]" or
"load [jar]" there, as described in
javadocs to
org.codehaus.groovy.tools.LoaderConfiguration,
found in Groovy source distribution;
before starting groovy set
groovy.starter.conf.override system
property to the name of that file,
like
-Dgroovy.starter.conf.override=[filename]

Resources