/*
* In this example, Yoko Harada shows an example of how to invoke various types of
* methods in JRuby. Some of the features of the JSR-223 API is being able to
* pass multiple arguments to a Ruby method, getting back various types, and even
* returning multiple types back (Ruby allows for multiple return values).
*
* InvokeFunction() is similar to invokeMethod(), where invokeFunction calls
* functions in the global namespace, also called the top-level namespace. See
* the invokeMethod() example for calling methods held within a class or module.
*
* See the blog entry:
* http://yokolet.blogspot.com/2008/03/tips-for-jruby-engine-how-to-invoke.html
*/
package klauer.callingruby.yokoharada;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
*
* @author Yoko Harada
*/
public class InvokingFunctionsExample {
/**
* There is one exception to method calls in Ruby that cannot be done using
* the invokeFunction() and invokeMethod() calls: Blocks. Since ruby can
* yield to another method a block of code to execute, these types of methods
* will not work with this API. According to yoko, there is no way to do this
* currently in Java.
*
* Perhaps with the ongoing closures debate in Java 7 we might see this functionality
* fit in with the scripting API.
*/
/**
* This constructor creates the basis for this set of methods explaining how to
* invoke various types of methods in JRuby.
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private InvokingFunctionsExample()
throws ScriptException, NoSuchMethodException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("jruby");
invokeSimpleFunction(engine);
invokeFunctionWithArguments(engine);
Map map = new HashMap();
map.put("ruby", "red");
map.put("pearl", "white");
map.put("rhino", "gray");
map.put("rose", "red");
map.put("nimbus", "gray");
map.put("gardenia", "white");
map.put("camellia", "red");
invokeFunctionWithReturns(engine, map);
invokeFunctionWithGlobalVariables(engine, map);
}
/**
* A simple funciton taking no arguments. The script is provided, in the string,
* of course.
* @param engine
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeSimpleFunction(ScriptEngine engine)
throws ScriptException, NoSuchMethodException {
String script =
"def say_something()" +
"puts \"I\'m sleepy because I went to bed three in the morning!\"" +
"end";
engine.eval(script);
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("say_something");
}
/**
* This will invoke a Ruby method with multiple arguments. Since Ruby can
* take in a parameter with various methods (see *list), we can still
* pass to the invokeFunction() just passing it multiple parameters as we
* see fit.
* @param engine
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeFunctionWithArguments(ScriptEngine engine)
throws ScriptException, NoSuchMethodException {
String script =
"def come_back(type, *list)" +
"print \"#{type}: #{list.join(\',\')}\";" +
"print \"...\";" +
"list.reverse_each {|l| print l, \",\"};" +
"print \"\n\";" +
"end";
engine.eval(script);
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("come_back",
"sol-fa",
"do", "re", "mi", "fa", "so", "ra", "ti", "do");
}
/**
* This Ruby script will return a subset of the methods passed into it,
* essentially multiple return values. We can handle this in the API,
* since all invokeFunction() calls return java.lang.Object, which we can
* cast. If the function returns multiple values, we can cast this return-type
* as a List. @see printValues()
* @param engine
* @param map
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeFunctionWithReturns(ScriptEngine engine, Map map)
throws ScriptException, NoSuchMethodException {
String script =
"def get_by_value(hash, value)" +
"hash.select { |k,v| v == value }" +
"end";
engine.eval(script);
Invocable invocable = (Invocable) engine;
Object object = invocable.invokeFunction("get_by_value", map, "red");
printValues("red", object);
object = invocable.invokeFunction("get_by_value", map, "gray");
printValues("gray", object);
}
/**
* From the invokeFunctionWithReturns() method, we can parse a function that
* returns multiple values by casting it as an instanceof a List. See below.
* @param value
* @param object
*/
private void printValues(String value, Object object) {
System.out.print(value + ": ");
if (object instanceof List) {
for (Object element : (List) object) {
if (element instanceof List) {
for (Object param : (List) element) {
System.out.print(param + " ");
}
}
}
}
System.out.println();
}
/**
* Yoko Harada spoke about how cluttered passing multiple values can be in your
* script. To solve this, you can place these variables as global variables
* in your namespace in Ruby, which the script will have access to in its
* methods. See the method and script below.
* @param engine
* @param map
* @throws javax.script.ScriptException
* @throws java.lang.NoSuchMethodException
*/
private void invokeFunctionWithGlobalVariables(ScriptEngine engine, Map map)
throws ScriptException, NoSuchMethodException {
String script =
"def get_by_value(value)" +
"$hash.select { |k,v| v == value }" +
"end";
engine.put("hash", map);
engine.eval(script);
Invocable invocable = (Invocable) engine;
Object object = invocable.invokeFunction("get_by_value", "white");
printValues("white", object);
}
public static void main(String[] args)
throws ScriptException, NoSuchMethodException {
new InvokingFunctionsExample();
}
}