Monday, April 28, 2008

Invocable#getInterface with two interfaces

I wonder that even though multiple Java interfaces are mixed in and implemented by Ruby, whether only one invocation of getInterface method would be enough or not. To figure out what would happen, I wrote two interfaces below:
package mountainash;

public interface FlowerAttribute {
String getName();
String getColor();
double getPrice();
}

package mountainash;

public interface Flowers {
FlowerAttribute selectFlower(String name, String color);
}
Then, I wrote Ruby script implementing above interfaces like this:
# 
# Florist.rb

require 'java'

class Flower
import 'mountainash.FlowerAttribute'
def initialize(name, color, price)
@name = name
@color = color
@price = price
end
def getName
@name
end
def getColor
@color
end
def getPrice
@price
end
end

class Florist
import 'mountainash.Flowers'
def initialize
@list = [Flower.new("rose", "red", 1.99),
Flower.new("rose", "pink", 1.59),
Flower.new("tulip", "red", 0.99),
Flower.new("tulip", "pink", 1.09)]
end
def selectFlower(name, color)
@list.each { |flower| if flower.getName == name &&
flower.getColor == color
then return flower end }
end
end
Florist.new
After evaluating the script and invoking getInterface method, we will get just one mountainash.Flowers type object in Java code.
Object obj = parsedScript.eval(scriptContext);
Flowers flowers = ((Invocable)engine).getInterface(obj, Flowers.class);
Do we need to use getInterface method again to get FlowerAttribute type object, like this?
Object obj2 = flowers.selectFlower(name, color);
FlowerAttribute attribute = ((Invocable)engine).getInterface(obj2, FlowerAttribute.class);
The answer was no. In this case, just one invocation of getInterface method worked perfect.
Object obj = parsedScript.eval(scriptContext);
Flowers flowers = ((Invocable)engine).getInterface(obj, Flowers.class);
FlowerAttribute attribute = flowers.selectFlower("rose", "red");

However, I haven't tested every possible case, there might be cases getInterface method needs to be invoked multiple times. Or, older version of JRuby might have caused some trouble. I tested this on JRuby 1.1.1 and didn't have any problem.

The entire code I tried is:
package mountainash;

import java.io.*;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import javax.servlet.*;
import javax.servlet.http.*;

public class FloristServlet extends HttpServlet {

private ScriptEngine engine;
CompiledScript parsedScript;

@Override
public void init() {
try {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName("jruby");

ClassLoader loader = getClass().getClassLoader();
InputStream iStream = loader.getResourceAsStream("ruby/Florist.rb");
Reader reader = new InputStreamReader(iStream, "UTF-8");
parsedScript = ((Compilable) engine).compile(reader);
} catch (ScriptException ex) {
Logger.getLogger(FloristServlet.class.getName()).log(Level.SEVERE, null,
ex);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(FloristServlet.class.getName()).log(Level.SEVERE, null,
ex);
}
}

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
SimpleScriptContext scriptContext = new SimpleScriptContext();
scriptContext.setWriter(out);
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet FloristServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>Servlet FloristServlet at " + request.getContextPath() + "</h3>");
out.println("<pre>");
Object obj = parsedScript.eval(scriptContext);
Flowers flowers = ((Invocable)engine).getInterface(obj, Flowers.class);
FlowerAttribute attribute = flowers.selectFlower("rose", "red");
out.print(getString(attribute));
out.println("<br/>");
attribute = flowers.selectFlower("rose", "pink");
out.print(getString(attribute));
out.println("<br/>");
attribute = flowers.selectFlower("tulip", "red");
out.print(getString(attribute));
out.println("<br/>");
attribute = flowers.selectFlower("tulip", "pink");
out.print(getString(attribute));
out.println("</pre>");
out.println("</body>");
out.println("</html>");
} catch (ScriptException ex) {
Logger.getLogger(FloristServlet.class.getName()).log(Level.SEVERE, null, ex);
} finally {
out.close();
}
}

private String getString(FlowerAttribute attribute) {
return attribute.getName() +
"(" + attribute.getColor() +
") : $" + attribute.getPrice();
}

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}
Mountainash web application directory hierarchy is below:
+ Mountainash
+ WEB-INF
+ src
+ mountainash
- FloristServlet.java
- FlowerAttribute.java
- Flowers.java
+ ruby
- FLorist.rb
+ lib
- jruby.jar
- jruby-engine.jar
- script-api.jar
+ classes -+
+ mountainash | automatically created
- FloristServlet.class | directories and files
- FlowerAttribute.class | by IDE
- Flowers.class |
+ ruby |
- Florist.rb -+
Correct output from FloristServlet will be:
<html>
<head>
<title>Servlet FloristServlet</title>
</head>
<body>
<h3>Servlet FloristServlet at /Mountainash</h3>
<pre>
rose(red) : $1.99<br/>
rose(pink) : $1.59<br/>
tulip(red) : $0.99<br/>
tulip(pink) : $1.09</pre>
</body>
</html>

No comments: