Skip to content

Commit

Permalink
Merge branch 'nashorn'
Browse files Browse the repository at this point in the history
Java 8 removed the Rhino implementation of JavaScript
in favor of the newer, faster, better, shinier, and
backwards-incompatible Nashorn implementation.

Fortunately, Nashorn provides a backwards compatibility
script called mozilla_compat.js which we can source to
regain the most common Mozilla-specific behavior.

Fixes imagej/ImageJ#116.
  • Loading branch information
ctrueden committed Jun 11, 2015
2 parents 965b097 + 8c05f67 commit 167d606
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 14 deletions.
Expand Up @@ -59,18 +59,47 @@ public JavaScriptScriptLanguage() {
super("javascript");
}

// -- JavaScriptScriptLanguage methods --

/**
* Returns true iff this script language is using the <a
* href="http://openjdk.java.net/projects/nashorn/">Nashorn</a> JavaScript
* engine. This is the case for Java 8.
*/
public boolean isNashorn() {
return getEngineName().contains("Nashorn");
}

/**
* Returns true iff this script language is using the <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino"
* >Rhino</a> JavaScript engine. This is the case for Java 6 and Java 7.
*/
public boolean isRhino() {
return getEngineName().contains("Rhino");
}

// -- ScriptEngineFactory methods --

@Override
public ScriptEngine getScriptEngine() {
final ScriptEngine engine = super.getScriptEngine();
try {
engine.eval("function load(path) {\n"
+ " importClass(Packages.sun.org.mozilla.javascript.internal.Context);\n"
+ " importClass(Packages.java.io.FileReader);\n"
+ " var cx = Context.getCurrentContext();\n"
+ " cx.evaluateReader(this, new FileReader(path), path, 1, null);\n"
+ "}");
if (isNashorn()) {
// for Rhino compatibility, importClass and importPackage in particular
engine.eval("load(\"nashorn:mozilla_compat.js\");");
}
if (isRhino()) {
// for the load function, which is somehow otherwise unavailable (?)
engine.eval("function load(path) {\n" +
" importClass(Packages." + contextClass(engine) + ");\n" +
" importClass(Packages.java.io.FileReader);\n" +
" var cx = Context.getCurrentContext();\n" +
" cx.evaluateReader(this, new FileReader(path), path, 1, null);\n" +
"}");
}
}
catch (ScriptException e) {
catch (final ScriptException e) {
e.printStackTrace();
}
return engine;
Expand Down Expand Up @@ -112,4 +141,24 @@ public Object decode(final Object object) {
return null;
}

// -- Helper methods --

private String contextClass(final ScriptEngine engine) {
if (isNashorn()) return "jdk.nashorn.internal.runtime.Context";

final String engineClassName = engine.getClass().getName();

if (isRhino()) {
if (engineClassName.startsWith("com.sun.")) {
// assume JDK-flavored Rhino script engine
return "sun.org.mozilla.javascript.internal.Context";
}
// assume vanilla Mozilla-flavored Rhino script engine
return "org.mozilla.javascript.Context";
}

throw new UnsupportedOperationException("Unknown JavaScript flavor: " +
engineClassName);
}

}
Expand Up @@ -67,10 +67,7 @@ public void testBasic() throws InterruptedException, ExecutionException,
final Context context = new Context(ScriptService.class);
final ScriptService scriptService = context.getService(ScriptService.class);
final String script = "$x = 1 + 2;";
// NB: Some JVMs return Integer, others Double. Let's be careful here.
final ScriptModule m = scriptService.run("add.js", script, true).get();
final Number result = (Number) m.getReturnValue();
assertEquals(3.0, result.doubleValue(), 0.0);
assertResult(3.0, scriptService.run("add.js", script, true).get());
}

@Test
Expand All @@ -80,7 +77,9 @@ public void testLocals() throws ScriptException {

final ScriptLanguage language = scriptService.getLanguageByExtension("js");
final ScriptEngine engine = language.getScriptEngine();
assertTrue(engine.getClass().getName().endsWith(".RhinoScriptEngine"));
final String engineClassName = engine.getClass().getName();
assertTrue(engineClassName.endsWith(".RhinoScriptEngine") ||
engineClassName.endsWith(".NashornScriptEngine"));
engine.put("$hello", 17);
assertEquals("17", engine.eval("$hello").toString());
assertEquals("17", engine.get("$hello").toString());
Expand Down Expand Up @@ -122,8 +121,32 @@ public void testLoad() throws IOException, InterruptedException, ExecutionExcept
final Context context = new Context(ScriptService.class);
final ScriptService scriptService = context.getService(ScriptService.class);
final String script = "load('" + tmp.getPath() + "'); three();";
final Object result = scriptService.run("three.js", script, false).get().getReturnValue();
assertEquals(4.0, (Number) result);
assertResult(4.0, scriptService.run("three.js", script, false).get());
assertTrue(tmp.delete());
}

@Test
public void testJavaAPI() throws InterruptedException, ExecutionException,
IOException, ScriptException
{
final Context context = new Context(ScriptService.class);
final ScriptService scriptService = context.getService(ScriptService.class);
final String script = //
"importClass(Packages.java.util.ArrayList);\n" //
+ "var list = new ArrayList();\n" //
+ "list.add(3);\n" //
+ "list.add(5.5);\n" //
+ "list.add(7);\n" //
+ "list.get(1);\n";
assertResult(5.5, scriptService.run("javaAPI.js", script, true).get());
}

// -- Helper methods --

private void assertResult(final double expected, final ScriptModule m) {
// NB: Some JVMs return Integer, others Double. Let's be careful here.
final Number result = (Number) m.getReturnValue();
assertEquals(expected, result.doubleValue(), 0.0);
}

}

0 comments on commit 167d606

Please sign in to comment.