/*
* This class shows how you can use the JSR-223 Scripting API in Java 6 to
* call Ruby methods using the invokeMethod() call. This is similar to the
* invokeFunction() Example seen, however, invokeMethod() allows you to call
* methods and functions that are defined in a class or module.
*
* See the blog entry for this on Yoko Harada's blog:
* http://yokolet.blogspot.com/2008/03/tips-for-jruby-engine-invokemethod.html
*/
package klauer.callingruby.yokoharada;
import java.util.List;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
*
* @author Yoko Harada
*/
public class InvokingMethodsExample {
/**
* Sets up the set of examples by creating the JRuby scripting instance.
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private InvokingMethodsExample()
throws ScriptException, NoSuchMethodException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("jruby");
//invokeSimpleMethod(engine);
invokeMethodWithMultipleInstances(engine);
//invokeMethodWithGlobalVariables(engine);
}
/**
* This is the simplest example for calling a method inside a class or module
* In this example, we have a module SomeModule defined that has a method,
* say(). To call this method, we first create an Invocable type, returning
* an instance of the module itself. From here we can call the method inside
* the Invocable module.
* @param engine
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeSimpleMethod(ScriptEngine engine)
throws ScriptException, NoSuchMethodException {
String script =
"module SomeModule\n" +
"def say()" +
"puts \"Hi, there!\"" +
"end\n" +
"end\n" +
"class SomeClass\n" +
"include SomeModule;" +
"end\n" +
"SomeClass.new";
// We must eval the script prior to having access to it. This must be
// done for all class- and module-level access. It may be a performance
// penalty, but I think that's why JRuby has a compiler now--jrubyc.
Object object = engine.eval(script);
Invocable invocable = (Invocable) engine;
invocable.invokeMethod(object, "say");
script =
"class AnotherClass\n" +
"def say_it_again()" +
"puts \"OK. I said, \'Hi, there.\'\"" +
"end\n" +
"end\n" +
"AnotherClass.new";
// We do this again to show another example (reusing the Object).
object = engine.eval(script);
invocable.invokeMethod(object, "say_it_again");
}
/**
* If we wanted to get more than one instance from a Ruby return method, we can take
* advantage of Ruby returning to us a java.util.List object that we can parse
* for the instances we want to use. In this case, we are going to get back
* two instances which we will execute methods on.
*
* In this example, we will get back two items from Ruby's script return,
* and with each of these we will call the Ruby script comment() and others().
* @param engine
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeMethodWithMultipleInstances(ScriptEngine engine)
throws ScriptException, NoSuchMethodException {
String script =
"class Flowers\n" +
"@@hash = {\'red\' => \'ruby\', \'white\' => \'pearl\'}\n" +
"def initialize(color, names)" +
"@color = color;" +
"@names = names;" +
"end\n" +
"def comment\n" +
"puts \"#{@names.join(\', \')}. Beautiful like a #{@@hash[@color]}!\"" +
"end\n" +
"def others(index)" +
"print \"If I omit #{@names[index]}, \";" +
"@names.delete_at(index);" +
"print \"others are #{@names.join(\', \')}.\n\"" +
"end\n" +
"end\n" +
"red = Flowers.new(\"red\", [\"cameliia\", \"hibiscus\", \"rose\", \"canna\"])\n" +
"white = Flowers.new(\"white\", [\"gardenia\", \"lily\", \"daisy\"])\n" +
"return red, white";
Object objects = engine.eval(script);
Invocable invocable = (Invocable) engine;
if (objects instanceof List) {
for (Object object : (List) objects) {
invocable.invokeMethod(object, "comment");
invocable.invokeMethod(object, "others", 1);
}
}
}
/**
* As shown in invodeMethodWithMultipleInstances(), we can get back multiple
* instances of a script as a java.lang.Object, casting it to a List. This allows
* us access to each of the returned instances. An alternative method to doing this
* is to assign these values to the global variable namespace. In Ruby, this is
* done by prepending the variables with '$'. From this, we can call the script
* engine to get back to us the visible global variables.
*
* This example does nearly the same as
* {@link InvokingMethodsExample#invokeMethodWithMultipleInstances(javax.script.ScriptEngine) },
* except we assign the values to a global namespace instead. The flexibility
* for us comes from Java invoking the engine to get at each instance.
* @param engine
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeMethodWithGlobalVariables(ScriptEngine engine)
throws ScriptException, NoSuchMethodException {
String script =
"class Flowers\n" +
"@@hash = {\'red\' => \'ruby\', \'white\' => \'pearl\'}\n" +
"def initialize(color, names)" +
"@color = color;" +
"@names = names;" +
"end\n" +
"def comment\n" +
"puts \"#{@names.join(\', \')}. Beautiful like a #{@@hash[@color]}!\"" +
"end\n" +
"def others(index)" +
"print \"If I omit #{@names[index]}, \";" +
"@names.delete_at(index);" +
"print \"others are #{@names.join(\', \')}.\n\"" +
"end\n" +
"end\n" +
"$red = Flowers.new(\"red\", [\"cameliia\", \"hibiscus\", \"rose\", \"canna\"])\n" +
"$white = Flowers.new(\"white\", [\"gardenia\", \"lily\", \"daisy\"])";
engine.eval(script);
Object red = engine.get("red");
Object white = engine.get("white");
Invocable invocable = (Invocable) engine;
invocable.invokeMethod(red, "comment");
invocable.invokeMethod(white, "comment");
invocable.invokeMethod(red, "others", 1);
invocable.invokeMethod(white, "others", 2);
}
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
new InvokingMethodsExample();
}
}