Monday, November 03, 2008

Recent update of JRuby engine

Recently, I updated CVS repo of JSR 223 JRuby engine to fix issue 39 and 40 filed in the issue tracker.

Issue 39 was i18n related bug. To determine a character encoding for Writer type instances, I have put sun.jnu.encoding before file.encoding. However, I knew that some platforms had different values in thier sun.jnu.encoding property from file.encoding by the bug report. Since I don't have such kind of platforms at all, I haven't noticed the bug so far. JRuby determines the output encoding from input file's encoding or applies file.encoding if Strings are direcly given to JRuby's eval method. Thus, JRuby engine shoud have seen file.encoding only.

At the same time, I added a small fix so that JRuby engine sees the encoding set in OutputStreamWriter type instances. This fix enables users to get preferable output when they execute scripts whose encodings are not default one. For example, my platform's default encoding is UTF-8, and I want to run a Ruby script written in Shift_JIS encoding, what would happen? In this case, JRuby always outputs in Shift_JIS, and JRuby script engine can't change the encoding that JRuby applies. Naturally, outputs would be unreadable characters since terminals usually display platoform's default encoding only. What if outputs are written in a file in a correct character encoding? We surely have some way of checking it. To see Shift_JIS files on UTF-8 platform, I'll execute the command "nkf -Sw file."

This is an example to use this feature:
import com.sun.script.jruby.JRubyScriptEngineManager;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

public class NonDefaultEncodingExample {

private String outputFilename = "output.txt";
private String errorFilename = "error.txt";
private String scriptName = "/Users/yoko/NetBeansProjects/RubyTestShiftJIS/lib/main.rb";

private NonDefaultEncodingExample() throws ScriptException, IOException {
//ScriptEngineManager manager = new ScriptEngineManager();
JRubyScriptEngineManager manager = new JRubyScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("jruby");
Reader reader = new FileReader(scriptName);
OutputStreamWriter writer =
new OutputStreamWriter(new FileOutputStream(outputFilename), "Shift_JIS");
FileWriter errorWriter = new FileWriter(errorFilename);
writer.write("[outputs]\n");
errorWriter.write("[errors]\n");

ScriptContext context = new SimpleScriptContext();
context.setWriter(writer);
context.setErrorWriter(errorWriter);
context.setAttribute(ScriptEngine.FILENAME, scriptName, ScriptContext.ENGINE_SCOPE);
try {
engine.eval(reader, context);
} finally {
writer.close();
errorWriter.close();
}
}

public static void main(String[] args) throws ScriptException, IOException {
new NonDefaultEncodingExample();
}
}


It is very, very important to set filename by using ScriptEngine.FILENAME attribute name. As I wrote in the section 3. JRuby vs. JRuby script engine of my old post, http://yokolet.blogspot.com/2008/10/why-did-i-get.html, JRuby engine hands String read from Reader type instance if filename is not given. This means JRuby loses the chance to know original script's encoding and just applies platform's default encoding even though given String has another encoding. When JRuby supports Ruby 1.9.x and its encoding declaration, this not nice mechanism would disappear.

Issue 40 is related to termination control. Since javax.script API doesn't have terminate() method like BSF, a choice was to execute JRuby's terminate() method always or never execute it. Many of Ruby test scripts have at_exit block, which gets run when JRuby's terminate() method is invoked, so I chose the former one. JRuby's terminate() method terminates a local scope, too. For this reason, JRuby didn't keep local scopes over the eval methods. To improve this, I introduced com.sun.script.jruby.terminator System property, whose values is "on" or "off." When terminator is turned off, JRuby engine doesn't execute JRuby's terminate() method, so variables in the local scope are available to keep using. I tested this as in below, which was based on the code posted to jruby-users ML before:
import com.sun.script.jruby.JRubyScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class LocalScopeTest {

private LocalScopeTest() throws ScriptException {
//ScriptEngineManager manager = new ScriptEngineManager();
JRubyScriptEngineManager manager = new JRubyScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("jruby");
System.setProperty("com.sun.script.jruby.terminator", "off");
engine.eval("$x='sun global'");
engine.eval("puts \"$x = #{$x}\"");
engine.eval("at_exit { puts \"in an at_exit block\" }");

engine.eval("x='sun local'");
engine.eval("puts \"x = #{x}\"");
System.setProperty("com.sun.script.jruby.terminator", "on");
engine.eval(new String());
}

public static void main(String[] args) throws ScriptException {
new LocalScopeTest();
}
}

When I ran this code, I got this output:
$x = sun global
x = sun local
in an at_exit block

Again, javax.script API doesn't have terminte() method, so JRuby engine should have some alternative to execute terminate() when it needs to be invoked. Look at the last two lines of LocalScopeTest constructor. I turned on terminator switch and evaled empty String. These are the alternative way of invoking terminate() method. By default, terminator switch is set "on", so JRuby engine should work the same as before it was. Adding this feature would not affect the program worked before. If you find bugs, feel free to file them in the issue tracker at https://scripting.dev.java.net/servlets/ProjectIssues.

This bug fixed version has not been released yet since JRuby 1.1.5 seems to be out soon. After checking JRuby engine works on upcoming JRuby 1.1.5, I will release next version.

Right after I wrote this entry, JRuby 1.1.5 was out there. So, I released JRuby engine 1.1.6 at https://scripting.dev.java.net/servlets/ProjectDocumentList?folderID=8848&expandFolder=8848&folderID=0 after I checked whether new version worked fine or not.