Using JavaScript from Java in Rhino 2021 version

There should have been quite a few cases where you wanted to run the JavaScript engine in the JavaVM and use JavaScript as the DSL, but the JavaScript engine Nashorn that comes standard with the JavaVM is already deprecated in Java11. , It is a source of anxiety for those who use it in production.

The reason Nashorn is deprecated is that it can't keep up with the latest ECMAScript specification.

Certainly there is a motivation to use modern ES, but if that becomes a shackle and the speed of evolution of the JVM itself is controlled, it is reasonable to decide to separate it from the pace of evolution of the JVM itself as deprecation. Target.

Then what should I do? The first option is to use the multilingual JVM GraalVM, which is being developed as the Alt JVM. GraalVM is mentioned in various places, so I won't touch it here.

In actual operation, it is not possible to change the VM and operate it, but it is somewhat disappointing that the Java program does not work unless it is a specific JVM. Let's try Rhino, the standard for pre-Nashorn JS on JVM.

https://github.com/mozilla/rhino

If you take a look at the Rhino repository for the first time in a while, you'll see that the latest 1.7.13 was released in September 2020 and is still well maintained.

Looking at the Compatibility Table with ES2015, it seems that it is quite difficult, but the level of compatibility is gradually improving, so it seems that we can expect it in the future.

Use Rhino with ScriptEngine

Rhino 1.7.13 supports the ScriptEngine interface of the JVM. Indeed, Nashorn can now be replaced as is.

To use the ScriptEngine interface with Rhino, you need rhino-engine.jar in addition to the traditional rhino.jar.

Therefore, describe the following dependency in pom.xml. (For Maven)

<!-- https://mvnrepository.com/artifact/org.mozilla/rhino -->
<dependency>
    <groupId>org.mozilla</groupId>
    <artifactId>rhino</artifactId>
    <version>1.7.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mozilla/rhino-engine -->
<dependency>
    <groupId>org.mozilla</groupId>
    <artifactId>rhino-engine</artifactId>
    <version>1.7.13</version>
</dependency>

The simplest way to execute JavaScript would be code like this: You can use it as it is just by specifying rhino for the engine name.

ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("rhino");
try {
    scriptEngine.eval("function add(a, b) { return a + b }");
    Number v = (Number) scriptEngine.eval("add(13, 17)");
    System.out.println(v);
} catch (ScriptException e) {
    e.printStackTrace();
}

However, in the case of Nashorn, the actual type of Number in the return value of the above code was Double, but in the case of Rhino, it has changed to Long. There seems to be various differences in the details.

Interoperate with Java objects

In order to use a Java object from within JavaScript, it is necessary to set the object in the scope of JavaScript and call it.

Map<String, Object> map = new HashMap<>();
map.put("a", 10);
map.put("b", 20);
map.put("console", new MyConsole());
SimpleBindings bindings = new SimpleBindings(map);

scriptEngine
  .eval("c = a + b;"
      + "a += b;"
      + "console.log('a=' + a + ', b=' + b + ', c=' + c);",
      bindings);

Here, MyConsole is a class created by myself as follows.

public class MyConsole {

    public void log(Object arg) {
        System.out.println(String.valueOf(arg));
    }
}

Result is

a=30, b=20, c=30

It was as expected.

Use Rhino raw

As with Rhino prior to 1.7.12, it is possible to use Rhino raw without going through the ScriptEngine interface.

The code in this case is as follows.

Context context = new ContextFactory().enterContext();
Scriptable globalScope = context.initStandardObjects();

Script script = null;
try (Reader reader = new FileReader(jsSourceFile)) {
    script = context.compileReader(reader, jsSourceFile.getName(), 1, null);
} catch (IOException | EvaluatorException e) {
    e.printStackTrace(System.err);
    System.exit(1);
}

ScriptableObject.putProperty(globalScope, "a", 10);
ScriptableObject.putProperty(globalScope, "b", 20);
ScriptableObject.putProperty(globalScope, "console", new MyConsole());

// run global scope context
script.exec(context, globalScope);

Pay attention to the comparison of strings

When I wrote a program that manipulates Java objects with some JavaScript, I encountered the following cases.

var javaObject = someObject.getValue(); //javaObject is java.lang.String

console.log(javaObject); // "OK"

if (javaObject === 'OK') { // false
    //Not equated with JavaScript string literals
    console.log('===');
} else if (javaObject == 'OK') { // false
    // ==But no
    console.log('==');
} else if ('OK'.equals(javaObject)) { // true
    console.log('equals');
}

The result is equals. Java strings and JavaScript strings are different. This needs attention.

Addendum

context.getWrapFactory().setJavaPrimitiveWrap(false);

You can change the behavior around here by calling>.

Summary

Rhino is steadily evolving. Even in 2021, there is no worry about adopting JavaScript as the DSL in Java applications.

Recommended Posts

Using JavaScript from Java in Rhino 2021 version
Call Java method from JavaScript executed in Java
Use "Rhino" which runs JavaScript in Java
Try using Sourcetrail (win version) in Java code
Try using Sourcetrail (macOS version) in Java code
Try using RocksDB in Java
Using Docker from Java Gradle
JavaScript as seen from Java
Java version notation that changes in Java 10
Encrypt using RSA cryptography in Java
Java, JavaScript, C # (difference in assignment)
HTTPS connection using tls1.2 in Java 6
I tried using JWT in Java
The road from JavaScript to Java
Sample code using Minio from Java
[Java] Get data from DB using singleton service in Spring (Boot)
Study Deep Learning from scratch in Java.
I tried using Elasticsearch API in Java
Data processing using stream API from Java 8
OCR in Java (character recognition from images)
Try using the Stream API in Java
Map without using an array in java
Reverse Key from Value in Java Map
ERRORCODE = -4471 occurs in Java application using Db2.
Try using JSON format API in Java
Connect from Java to MySQL using Eclipse
Read Felica using RC-S380 (PaSoRi) in Java
Call Java methods from Nim using jnim
Get history from Zabbix server in Java
Java version control using cask + anyenv + jenv
JSON in Java and Jackson Part ③ Embed JSON in HTML and use it from JavaScript
Gradle automatically generates version number from git and uses it in Java
Access Forec.com from Java using Axis2 Enterprise WSDL
GetInstance () from a @Singleton class in Groovy from Java
ChatWork4j for using the ChatWork API in Java
Partization in Java
[Java] API creation using Jerjey (Jax-rs) in eclipse
Java method call from RPG (method call in own class)
Java version check
Send email using Amazon SES SMTP in Java
Send push notifications using Notification Hubs in Java
Changes in Java 11
Rock-paper-scissors in Java
Try using GCP's Cloud Vision API in Java
Differences in writing Java, C # and Javascript classes
How to get Class from Element in Java
Text extraction in Java from PDF with pdfbox-2.0.8
Match IP addresses using regular expressions in Java
Capture and save from selenium installation in Java
Get unixtime (seconds) from ZonedDateTime in Scala / Java
Try accessing the dataset from Java using JZOS
Java and JavaScript
[Deep Learning from scratch] in Java 3. Neural network
Display "Hello World" in the browser using Java
Display "Hello World" in the browser using Java
Pi in Java
Try using the COTOHA API parsing in Java
NLP4J [001b] Morphological analysis in Java (using kuromoji)
Generate OffsetDateTime from Clock and LocalDateTime in Java
FizzBuzz in Java
[Java] Get KFunction from Method / Constructor in Java [Kotlin]