Nashorn: JavaScript amélioré en Java 8

Nashorn, prononcé «nass-horn», est l'allemand pour «rhinocéros», et c'est l'un des noms d'animaux pour un chasseur de chars allemand utilisé pendant la Seconde Guerre mondiale. C'est aussi le nom du remplacement - introduit avec Java 8 - de l'ancien moteur JavaScript Rhino lent. Rhino et Nashorn sont des implémentations du langage JavaScript écrit pour s'exécuter sur la machine virtuelle Java, ou JVM.

Rant obligatoire: JavaScript peut avoir Java dans son nom, mais les deux langages sont très différents dans l'esprit et la conception, ainsi que dans leurs implémentations. Néanmoins, une façon d'implémenter un interpréteur JavaScript est de compiler JavaScript en codes octets Java, ce que Rhino et Nashorn ont été conçus pour faire.

Vous pensez probablement à JavaScript en termes de scripts de navigateurs Web, et vous auriez raison pour la plupart. Il est également utilisé pour les serveurs. Par exemple, Node.js est utilisé pour créer des serveurs rapides et légers basés sur le moteur JavaScript V8 de Google Chrome. Les moteurs JavaScript des navigateurs Web ont accès au modèle d'objet de document HTML (DOM) et peuvent manipuler des éléments HTML via le DOM. Étant donné que différents navigateurs Web ont différents DOM et moteurs JavaScript, des frameworks tels que jQuery tentent de cacher les détails de l'implémentation au programmeur.

Nashorn, et Rhino avant lui, ne prennent explicitement pas en charge le DOM du navigateur. Implémentés sur la JVM, ils sont généralement appelés pour les scripts de l'utilisateur final dans les applications Java. Nashorn et Rhino peuvent être intégrés dans des programmes Java et utilisés comme shells de ligne de commande. Bien sûr, la magie supplémentaire nécessaire lorsque vous créez un script Java à partir de JavaScript est de relier les données et les incohérences de type entre les deux langages.

Problèmes avec Rhino

Le développement de Rhino a commencé chez Netscape en 1997 pour un projet malheureux "Javagator" et a été publié sur Mozilla.org en 1998. Il a ensuite été autorisé à Sun et à d'autres. Honnêtement, 1998 pourrait aussi bien être la période jurassique, tout comme le développement d'Internet - 16 ans plus tard, Rhino a clairement montré son âge. Selon Jim Laskey d'Oracle, le principal développeur de Nashorn:

Je suis sûr que tout cela est vrai, mais en tant que développeur et directeur de développement blasé, je trouve la dernière phrase très amusante. Après tout, les réécritures majeures ne sont jamais amusantes. Partir de zéro est toujours amusant.

Objectifs de Nashorn

Laskey a décrit ses objectifs pour Nashorn comme suit:

  • Nashorn sera basé sur la spécification du langage ECMAScript-262 Edition 5.1 et devra réussir les tests de conformité ECMAScript-262.
  • Nashorn prendra en charge l' javax.scriptAPI (JSR 223).
  • Un support sera fourni pour appeler du code Java à partir de JavaScript et pour Java pour appeler du code JavaScript. Cela inclut le mappage direct vers JavaBeans.
  • Nashorn définira un nouvel outil en ligne de commande,, jjspour évaluer le code JavaScript dans les scripts "shebang", ici les documents, et éditer les chaînes.
  • Les performances et l'utilisation de la mémoire des applications Nashorn devraient être nettement meilleures que celles de Rhino.
  • Nashorn n'exposera aucun risque de sécurité supplémentaire.
  • Les bibliothèques fournies doivent fonctionner correctement sous localisation.
  • Les messages d'erreur et la documentation seront internationalisés.

Laskey a également explicitement limité la portée du projet avec quelques "non-buts":

  • Nashorn ne prend en charge que ECMAScript-262 Edition 5.1. Il ne prendra en charge aucune fonctionnalité de l'édition 6 ni aucune fonctionnalité non standard fournie par d'autres implémentations JavaScript.
  • Nashorn n'inclura pas d'API de plug-in de navigateur.
  • Nashorn n'inclura pas la prise en charge de DOM / CSS ou des bibliothèques associées (telles que jQuery, Prototype ou Dojo).
  • Nashorn n'inclura pas de support de débogage direct.

Alors, que signifie être basé sur ECMAScript-262 Edition 5.1? La différence ici est que Rhino était basé sur l'ancienne édition 3. L' javax.scriptAPI (JSR 223) sert à rappeler JavaScript à partir de Java.

Le manque de prise en charge du débogage dans Nashorn est un pas en arrière par rapport à Rhino, qui possède son propre débogueur JavaScript. Cependant, vous trouverez des solutions de contournement pour cette omission délibérée dans au moins deux IDE populaires.

Outils de ligne de commande Nashorn: installation de jjs et jrunscript

Après avoir lu sur l'outil de ligne de commande de Nashorn jjs, j'étais impatient d'essayer le shell sur mon iMac, mais après avoir installé Java 8, il n'était pas disponible pour le shell bash. Il s'avère que la documentation et la mise en œuvre n'étaient pas complètement synchronisées.

Je savais que l'installation avait réussi:

 >java -version java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode) 

mais la course jjsest revenue -bash: jjs: command not found. Un peu de fouille m'a amené au /usr/bin/répertoire:

 >which java /usr/bin/java 

Là, j'ai trouvé quelque chose appelé jrunscript, qui s'est avéré être une variante de jjscelui qui exécute un script de démarrage supplémentaire. Cela aurait dû me satisfaire, mais j'étais perplexe quant à la raison pour laquelle l' jjsoutil documenté n'était pas installé /usr/bin/avec le reste de l'environnement d'exécution Java 8. Un peu de recherche m'a amené à regarder l' JavaVirtualMachinesinstallation pour Java 8. Sur un Mac, recherchez jjsin /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/ou /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/.

You can define an alias for jjs in the latter directory and add it to your shell configuration if you need it for scripting on a Mac or Linux. On a PC, you can add the correct jre/bin/ directory to your PATH. In his video from the Java 8 launch, Jim Laskey suggests copying jjs to the /usr/bin/ directory, but when I did that I found that jjs couldn't find the JRE properly at runtime.

Running JavaScript scripts

Why the two command-line tools for running JavaScript scripts? I'm not completely clear on what the development team was thinking, but jjs has capabilities that jrunscript doesn't, and jrunscript has an initialization file. Below are a few simple examples of jjs and jrunscript use.

 $ jrunscript nashorn> alert("hello, "); script error: ReferenceError: "alert" is not defined in  at line number 1 

This doesn't work because alert() is a browser/DOM function. D'oh! I could have sworn that worked in Rhino, though.

 nashorn> print("Hello, "); Hello,  

This does work because print() is a core JavaScript function.

 nashorn> var a = 1; nashorn> var b = "1"; nashorn> print (a+b); 11 nashorn> print(a+a); 2 nashorn> quit(); $ 

In other words, we have a basic REPL (read-execute-print-loop command-line) environment for JavaScript here. If you're surprised by the answer to a+b, consider this:

 nashorn> print (typeof(a+b)); string 

That's a charming side-effect of the loose typing and overloading of the "+" operator in JavaScript. It's correct behavior according to the JavaScript specification, not a bug.

Nashorn supports the "#" character as a leading line comment marker, so jjs and jrunscript can be used in executable "shebang" scripts written in JavaScript. On a Mac or Linux, you'll have to mark the JavaScript file as executable with the chmod utility to make it runnable.

You'll find a scripting mode in jjs that jrunscript seems to lack. In scripting mode, expressions inside back-ticks are passed to the outer shell for evaluation:

 $ jjs -scripting jjs> print ('ls'); Applications Applications (Parallels) Creative Cloud Files Desktop ... work jjs>

Scripting mode also enables an extension for "heredocs," which are basically multiline strings in a format familiar to Perl and Ruby programmers.

By the way, the arrow keys on the Mac keyboard don't work properly for line editing in the jjs shell. But there is a hack for that: You can brew install rlwrap and use that as part of your alias for jjs in your .bashrc or .zshrc file.

Calling JavaScript from Java

To call Nashorn JavaScript from a Java 8 program, you basically need to make a new ScriptEngineManager instance and use that ScriptEngineManager to load the Nashorn script engine by name. (See this Stack Overflow question for a pithy summary of loading and debugging Nashorn.)

Finally, you can pass the Nashorn engine a file or a string to evaluate:

 import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; ... try { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("nashorn"); engine.eval("load(\"" + "src" + "/" + "javascript_sample" + "/" + "test1.js" + "\");"); } catch (Exception ex) { //... } ... try { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("nashorn"); engine.eval("function hi(){\nvar a = 'PROSPER'.toLowerCase(); \nmiddle(); \nprint('Live long and' + a)}\n function middle(){\n var b = 1; for(var i=0, max = 5; i
    

Note that scripts can always generate ScriptException errors, so you need to catch them.

Calling Java from JavaScript

Calling Java from Nashorn is about as easy as it can be, since the Java 8 class libraries are built into Nashorn:

 print(java.lang.System.currentTimeMillis()); var file = new java.io.File("sample.js"); print(file.getAbsolutePath()); print(file.absolutePath); 

Note that Nashorn does not import the java package by default, because references to String or Object conflict with the corresponding types in JavaScript. Hence, a Java string is java.lang.String, not String.

Nashorn and JavaFX

If you invoke jjs with the -fx switch, it will allow you to use visual JavaFX classes in your Nashorn applications. For instance, the following example from the Oracle documentation displays a JavaFX button:

 var Button = javafx.scene.control.Button; var StackPane = javafx.scene.layout.StackPane; var Scene = javafx.scene.Scene; function start(primaryStage) { primaryStage.title = "Hello World!"; var button = new Button(); button.text = "Say 'Hello World'"; button.onAction = function() print("Hello World!"); var root = new StackPane(); root.children.add(button); primaryStage.scene = new Scene(root, 300, 250); primaryStage.show(); } 

Debugging Nashorn

I mentioned earlier that Nashorn doesn't include a debugger of its own. Fortunately, both NetBeans 8 and IntelliJ IDEA 13.1 support debugging Nashorn JavaScript. The Stack Overflow question I mentioned earlier includes a useful NetBeans 8 project that you can use as a sample. You'll find that simply using the debug item from the pop-up menu on JavaScript files will allow you to debug the Nashorn code.

In IntelliJ IDEA 13, you can set breakpoints in the Java and Nashorn JavaScript files using the same shortcut key (Com/Ctrl-F8). When you hit a JavaScript breakpoint, you get all the usual debugging information.

Nashorn was designed to be a better, faster replacement for the old Rhino engine, and by most measures it succeeds. It has some minor warts that I hope will be corrected in future updates, but for now there are reasonable hacks to let you use Nashorn effectively in your projects.