Is there a way to obtain reflection data on functions declared in a Groovy script that has been evaluated via a GroovyShell object? Specifically, I want to enumerate the functions in a script and access annotations attached to them.
Put this to the last line of Groovy script - it will serve as a return value from the script, a-la:
// x.groovy
def foo(){}
def bar(){}
this
Then, from Java code you can do the following:
GroovyShell shell = new GroovyShell();
Script script = (Script) shell.evaluate(new File("x.groovy"));
Now it seems that there's no option to introspect the annotations of Groovy script from Java directly. However, you can implement a method within the same Groovy script and call that one from Java code, for instance:
//groovy
def test(String m){
method = x.getMethod(m, [] as Class[])
assert method.isAnnotationPresent(X)
}
//java
script.getMetaClass().invokeMethod(script, "test", "foo");
After some experimenting, I found this to be the easiest way:
GroovyShell shell = new GroovyShell();
Script script = (Script)shell.parse(new FileReader("x.groovy"));
Method[] methods = script.getClass().getMethods();
The method array has all of the functions defined in the script and I can get the annotations from them.
Related
I know how to get the list of method names from a Ruby script from Java in JRuby. For example with this Java code:
ScriptingContainer container = new ScriptingContainer(LocalVariableBehavior.PERSISTENT);
container.setCompileMode(CompileMode.JIT);
Object receiver = container.runScriptlet(PathType.CLASSPATH, new File(<Ruby script path>);
String[] names = (String[]) container.runScriptlet("require 'java'; String.instance_methods.to_java :string");
But I would like to be able to have not only the names of the methods, but also their full signature. How is it possible to do that in JRuby?
I am currently working on a simple hello world program using jython and java.
The program is designed in way that a jython method accepts a name parameter and returns welcome message.
My problem is whenever I am accessing the jython method from java, it shows nullponter exception
My jython Script (JythonHello.py):
class JythonHello:
def __init__(self, name):
self.name = name
def sayHello(self):
return "Hello "+ self.name
and my java code:
public static void main(String[] args) {
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.execfile("src/jython/JythonHello.py");
PyObject callFunction = interpreter.get("sayHello");
PyObject result = callFunction.__call__(new PyString("Boban"));
String msg = (String) result.__tojava__(String.class);
System.out.println("output: " + msg);
}
Any suggestions?
Looking at your code; your python code defines a class and a member method:
class JythonHello:
def __init__(self, name): ...
def sayHello(self): ...
And it seems that you intend to call that method:
PyObject callFunction = interpreter.get("sayHello");
PyObject result = callFunction.__call__(new PyString("Boban"));
But please note: sayHello() doesn't take any arguments. That self parameter is an indication that you have to call it on an object; but without any other parameters!
So, in pure python you would say:
helloVar = JythonHello("Boban")
helloVar.sayHello()
But your java code tries to call it like
sayHello("Boban")
So, the real answer is: step back; and re-think what you really intend to do; and then write code that works that way.
I would start by not adding the "class" part on the python side; instead try to simply invoke a function that takes a string argument for example!
And finally: could it be that you are on the wrong path altogether? The main point of jython is to write simply python code to do "debug" work within a running JVM. You are writing complicated Java code to use a bit of python code on the other hand ...
I'm essentially trying to create a CLI with Groovy. I have a whole JavaFX GUI set up in Java and I want to be able to type in groovy script to run different functions inside a groovy script.
For example, say I have this script:
void meow() {
println "walrus"
}
I want to be able to type in "meow();" and press enter and evaluate it using the script as a reference.
I've tried using
shell.evaluate(inputStr, "src/Server/Scripting/CommandLineScript.groovy");
but to no avail; it just comes up with the error:
groovy.lang.MissingMethodException: No signature of method: CommandLineScript.meow() is applicable for argument types: () values: []
I can call other standard functions, such as:
shell.evaluate("println 'Hello World!';");
but I just can't run my own methods... How to solve it?
The following worked for me.
evaluate(new File("/Users/jellin/meow.groovy"))
I did change the meow.groovy file to execute the method within the file.
void meow() {
println "walrus"
}
meow()
One issue is I don't see a way to pass a parameter to the calling script.
I have used the following before, you can pass parameters as part of the binding.
String script = "full path to the script"
GroovyScriptEngine gse = new GroovyScriptEngine()
Binding binding = new Binding();
Object result = gse.run(script, binding)
Also, you might be able to simply reference the other scripts as classes and execute the run method on them.
There is also an AST transformation that can be used to have scripts extend a base script.
See here for more info
http://mrhaki.blogspot.com/2014/05/groovy-goodness-basescript-with.html
Thanks for your time guys; after a little more searching (always after posting a question do I find the answer in research >,<), I found that you can set a base class for the GroovyShell... I did it this way:
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
loader.addClasspath("src/ScriptLoc/");
binding = new Binding();
CompilerConfiguration compConfig = new CompilerConfiguration();
compConfig.setScriptBaseClass("ScriptName");
shell = new GroovyShell(loader, binding, compConfig);
I thought there would be a way to do it, and there it is... Now whenever I need to evaluate a script from the text box, I can just evaluate it and it evaluates it in the context of the base script.
I'm currently using the javax implementation of Rhino. By default Rhino uses a wrapper to return Java objects. Does Nashorn have similar behaviour or does it return JavaScript objects by default?
Thanks
Looks like it tries its best to return sensible objects. Using this code, then changing the XXX:
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName("nashorn");
engine.eval("function test() { return XXX; };");
Object result = ((Invocable)engine).invokeFunction("test");
System.out.println(result.getClass().getName());
Yields:
return 'hello world' = java.lang.String
return 1 = java.lang.Integer
return { name: 'Hello' } = jdk.nashorn.api.scripting.ScriptObjectMirror
Looks like that, even though the Java objects can be used within the JS code, it still references Java Objects (although they show up as function objects so there must be a wrapper there), we can't treat them as Javascript objects:
//"import"
var StringTokenizer = java.util.StringTokenizer;
print(typeof StringTokenizer);
var st = new StringTokenizer("this is a test");
print(typeof st);
java.util.StringTokenizer.prototype.name = 'myST';
print(st.name);
And here's the result:
testObj.js:9 TypeError: Cannot set property "name" of undefined
Now Javascript objects will be loaded as "jdk.nashorn.internal.scripts.JO" instances.
*If you want to test the above code more easily, just create an alias for your JDK's jjs (Nashorn Interpreter), e.g., if you create a file called test.js, you can run the program with:
$ jjs test.js
Mac OS = alias jjs=’/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/jjs’
Windows = Define an environment variable called ‘JAVA8_HOME’ and point to your jdk8 folder, then you can invoke jjs by running this command:
> “%JAVA8_HOME%\jre\bin\jjs” test.js
I have a Groovy file that looks like this (currently).
main.groovy
import org.packages.mystuff.JavaClassIAmUsing;
public class MyObject {
def rate(item){
def o = evaluate(new File (new File(getClass().protectionDomain.codeSource.location.path).parent),"CommonFunctions.groovy");
println o.whoami();
}
}
i have another groovy file called
CommonFunctions.groovy
def whoami() {return 'no body';}
I'm trying to include the CommonFunctions script in to main script, BUT the location of the script are not known at build time (i.e. i can not hardcode a absolute file path in the script or absoulte path of the java process in relation to where the scripts will be stored).
All i know is that the scripts will be together or at a location relative to the calling script (say sub directory).
I've attempted to try and location the calling script location, but i get the error
No signature of method: MyObject.evaluate()
How can i referance this script, considering the main script is accessed at runtime using a GroovyClassLoader.parseClass(File) method.
I'm not really sure why you want to do it this way, I think it would be much simpler to make a class of CommonsFunctions that you could instantiate normally and use everywhere.
However, it is possible to achieve what you want; with Groovy, there are not that many limitations...
There are two problems with your suggested solution:
getClass() inside your MyObject class naturally refers to ... the MyObject class, so your attempt to find the location of the script will fail. You're on the right track, but you need to resolve the script location using the surrounding Script class.
evaluate doesn't really work the way you think it does. The result of the evaluate method is the result of the script, not an instance of the Script class. One way to remedy this, is to rewrite the methods in CommonFunction as closure properties. These properties will be available in the shell Binding object when evaluating the script.
So, with these rewrites, you end up with something like this:
main.groovy
class MyObject {
def scriptDir
def rate(item) {
def commonFunctionsScriptFile = new File(scriptDir, "CommonFunctions.groovy")
def binding = new Binding()
new GroovyShell(binding).evaluate(commonFunctionsScriptFile)
println binding.variables.whoami()
}
}
scriptFile = new File(getClass().protectionDomain.codeSource.location.path)
new MyObject(scriptDir: scriptFile.parentFile).rate(null)
Here, the script file location is resolved in the script, not in the inner class.
CommonFunctions.groovy
whoami = { 'no body' }
Here, whoami is no longer a method, but a closure property which will be added to the binding. Make sure that you don't prefix this property with def, since then it will be a local variable instead of a property added to the binding object.
Output after these rewrites is the expected: no body.