Tout d'abord, je vous indique ici les import dont vous aurez besoin et qui seront à utiliser tout au long des exemples (mais pas utiles pour tous). Il ne seront pas réécris ensuite, pour plus de lisibilité :

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileReader;

Toujours pour simplifier le code affiché, les exceptions ne seront pas gérées, mais renvoyées par le main. Il vous faut évidemment avoir quelques bases de Java et de JavaScript pour pouvoir suivre ces exemples. Dans chacun d'eux, j'utiliserai des fonctions JavaScript. Si vous préférez utiliser du code orienté objet, il vous sera facile de l'adapter grâce aux exemples de la page Oracle citée en préambule.

Enfin, pour chaque exemple, je n'afficherai que la fonction main, qu'il faudra évidemment que vous incluiez dans une classe pour que le tout fonctionne, cependant les sources de chaque exemples sont disponibles en annexe de ce billet.

Exemple 1 : un simple affichage

Pour le premier essai, nous allons exécuter une simple ligne de code JavaScript dans du code Java :

public static void main(String[] args) throws Exception {
     ScriptEngineManager manager = new ScriptEngineManager();
     ScriptEngine engine = manager.getEngineByName("JavaScript");
     engine.eval("println('Mon premier script !');");
}

Rien de plus ! C'est en fait assez simple. Nous commençons par instancier un ScriptEngineManager qui nous permettra de charger le moteur de script souhaité. Nous l'utilisons sur la deuxième ligne : nous chargeons le moteur JavaScript. Aucune installation de bibliothèque nécessaire, car ce moteur est fourni avec Java 6 ! Ces deux premières lignes seront utilisées dans tous nos exemples. Nous terminons en demandant à notre moteur d'évaluer une ligne de code, ici l'affichage du texte "Mon premier script !".

Après compilation et exécution, la ligne s'affiche bien. Rien de transcendant ici, mais nous voyons que nous allons facilement pouvoir exécuter du code JavaScript.

Exemple 2 : interagir avec un script externe

Dans l'optique d'utiliser un système de plugins, il nous faut d'abord pouvoir sortir le code JavaScript du code Java, et le placer dans un fichier externe. De plus il nous faut pouvoir appeler une fonction en lui passant des paramètres, puis pouvoir récupérer le résultat obtenu.

Pour illustrer l'exemple, nous allons imaginer un programme Java qui passe à une fonction nommée compute() deux chiffres qu'elle traitera. Le code Java récupèrera le résultat du traitement effectué par compute() et l'affichera. Enfin, nous allons créer deux plugins JavaScript qui implémenteront tous les deux la fonction compute(). L'un fera une addition (plus.js) des deux chiffres donnés, l'autre une soustraction (moins.js). On commence par le code Java :

public static void main(String[] args) throws Exception {
     ScriptEngineManager manager = new ScriptEngineManager();
     ScriptEngine engine = manager.getEngineByName("JavaScript");
     engine.eval(new FileReader("plus.js"));
     Invocable inv = (Invocable) engine;
     System.out.println(inv.invokeFunction("compute",3,2));
}

Comme dans le premier exemple, nous récupérons le moteur JavaScript. Ensuite plutôt que directement évaluer du code, nous passons un fichier à la fonction eval(), qui contiendra notre code JavaScript. Nous commençons ici par plus.js, qui fera une addition. Vous pourrez ensuite utiliser moins.js. Enfin, nous invoquons la fonction compute() et lui passons deux arguments (3 et 2). Nous récupérons le résultat du traitement, qui est un Object, et qui contiendra ici un chiffre (un Double à vrai dire), nous l'affichons directement.

Voici le contenu de plus.js :

function compute(a,b) {
   return a+b;
}

Celui de moin.js :

function compute(a,b) {
   return a-b;
}

Aucun commentaire nécessaire ! Exécutez le programme avec plus.js, puis moins.js, les résultats seront 5.0 puis 1.0.

Nous voyons ici que nous pouvons très facilement avoir du code Java qui exécute une fonction dont il ne connait pas le contenu. Il en récupère le résultat sous forme d'un objet et peut le traiter. D'un autre coté, nous avons des fichiers contenant du code JavaScript qui traitent de façon différente deux arguments identiques, et renvoient le résultat. Dans une application gérant des plugins, il suffit alors que le nom du fichier JavaScript soit une variable, ou provienne d'un choix de l'utilisateur, et le tour est joué ! Attention tout de même, pour un système robuste, il est important de gérer les exceptions afin de réagir aux problèmes qui pourraient survenir.

Exemple 3 : Exporter des variables dans le script externe

Lorsque l'on mets en place un système de plugins, on souhaite que ces derniers puissent avoir accès aux informations de l'application, souvent par l'intermédiaire d'un noyau depuis lequel on accède à des variables. De cette manière le plugin agit de façon directe avec l'application, en consultant des informations, et en les mettant à jour. Nous allons dans cet exemple voir qu'il est tout à fait possible de faire cela avec Java et Rhino. Nous allons créer une classe Afficheur contenant la méthode affiche() qui se contente d'afficher dans la console une ligne de texte. Ensuite, nous allons instancier un objet de cette classe et l'exporter dans notre script JavaScript. Ce dernier utilisera dans son code l'objet exporté et appellera la méthode affiche() :

private static class Afficheur {
   public void affiche() {
       System.out.println("J'affiche !");
    }
}
public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval(new FileReader("afficheur.js"));
    Afficheur aff = new Afficheur();
    engine.put("monAfficheur",aff);
    Invocable inv = (Invocable) engine;
    inv.invokeFunction("affiche");
}

On voit ici qu'une fois l'objet Afficheur instancié, on l'exporte dans le script grace à la méthode put(). On indique que le nom de variable par laquelle l'objet sera accessible dans le script sera "monAfficheur". Enfin on appelle la fonction affiche() implémentée dans le script afficheur.js dont voici le contenu :

function affiche() {
   monAfficheur.affiche();
}

La fonction affiche() utilise donc la variable monAfficheur disponnible grace à l'export fait depuis le code Java, et appelle la méthode affiche(). Voilà notre liaison réalisée ! Depuis notre code JavaScript, nous accédons à un objet de notre application Java et pouvons l'utiliser. Ici nous ne réalisons qu'un simple affichage, mais tout est possible. Notez qu'il est possible de définir différent scope lors de l'export des variables permettant ainsi de leur donner des valeurs différentes selon le contexte utilisé lors de l'évaluation. Plus de renseignement dans la documentation.

Exemple 4 : Implémenter une interface

Nous progressons dans les fonctionnalités proposées pour réaliser une gestion de plugins. Mais il est possible d'aller plus loin, et de définir plus précisément les fonctionnalités offertes par les plugins. Pour cela, nous pouvons définir en Java une interface, et l'implémenter depuis un plugin. Une fois le code du plugin évalué, nous appelons une méthode depuis le code Java, dont l'implémentation a été réalisée depuis le code JavaScript ! Il devient alors possible de définir proprement en Java les méthodes qui seront à implémenter par les plugins, sans en connaitre le traitement évidemment, et d'utiliser ensuite dans le code des objets instanciant cette interface. Mieux que du blabla, le code : 

private interface Afficheur2 {
    void affiche2();
}
public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval("function affiche2() { println('affiche (2)'); }");
    Invocable inv = (Invocable) engine;
    Afficheur2 aff = inv.getInterface(Afficheur2.class);
    aff.affiche2();
}

Pour l'exemple, je n'utilise pas de fichier externe, mais directement une ligne de code JavaScript. Ce code implémente la méthode affiche2() de l'interface Afficheur2 en faisant un simple affichage de ligne. Nous créons un objet de type Afficheur2 puis récupérons l'instanciation de l'interface grâce à la méthode getInterface().

Enfin, nous utilisons ce nouvel objet et appelons la méthode affiche2() qui effectuera ce qui a été défini dans le code JavaScript, c'est à dire afficher une ligne de texte.

Nous voyons ici qu'il nous est donc possible d'écrire du code générique et de l'utiliser. C'est grâce aux plugins utilisés que instanciation est faite et nous pouvons ainsi pour un même code Java obtenir des comportement totalement différents.

Exemple 5 : Utilisation des classes Java depuis le code JavaScript

Comme dernier exemple et pour montrer toute la puissance de Rhino, nous créons un code Java qui se contentera d'évaluer un script :

public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval(new FileReader("frame.js"));
}

Le script en revanche est particulier, car il importe directement des classes Java et utilise les objets instanciés. Le script frame.js va créer une JFrame et l'afficher. Il affichera aussi le titre de la fenêtre, qu'il récupère directement depuis la propriété de la JFrame :

importClass(javax.swing.JFrame);
importClass(java.awt.Dimension);

var frame = new JFrame("hello");
frame.setSize(new Dimension(250,250));
frame.setVisible(true);
println(frame.title);

Exécutez le programme, une fenêtre apparait !

JFrame depuis un JavaScript


Voilà pour cette petite initiation au scripting avec Java et son application avec l'utilisation de plugins. Il y aurait surement beaucoup plus à dire, mais vous disposez maintenant d'une base pour débuter, et de l'alliance du Java et du JavaScript pour vos applications ! Ce sujet aura aussi été pour moi l'occasion de reprendre la plume sur ce blog, chose qui n'avait pas été faite depuis trop longtemps !


Fabien (eponyme)