Intéressé par des cours d'informatique en ligne ?
Visitez mon nouveau site https://www.yesik.it !

BeanShell est loin d'être un petit nouveau dans le monde Java. Et pourtant cet outil est souvent ignoré malgré un aspect bien sympatique. Cet article va donc essayer de vous convaincre que BeanShell peut être un ajout intéressant à votre trousse à outils Java.

BeanShell, c'est quoi?

BeanShell est un interpréteur Java disponible en standard (au moins dans l'installation de Java6 sous Debian). L'idée de BeanShell est de fournir un interpréteur Java pouvant être embarqué dans une application pour la rendre scriptable.

Mais BeanShell est aussi livré avec un interpréteur de commande (shell) autonome: bsh. Tout comme un shell Unix vous permet d'exécuter des tâches en invoquant des commandes Unix, le shell BeanShell vous permet d'exécuter des instructions écrites en Java. Cet outil se révèle donc bien pratique pour expérimenter du code Java. Ou à fortiori pour se familiariser avec ce langage.

Lancer et quitter l'interpréteur BeanShell

L'interpréteur bsh est un outil en ligne de commande. Pour l'invoquer, à partir d'un terminal tapez la commande suivante:

sh$ bsh
BeanShell 2.0b4 - by Pat Niemeyer (pat@pat.net)
bsh %

L'invite de commande (prompt) bsh % vous indique que l'interpréteur BeanShell attend vos ordres. Ou plus précisément des instructions BeanShell. La première à connaître est celle qui permet de quitter l'interpréteur:

bsh % exit();
sh$

Remarque:

Il existe aussi une version de l'interpréteur BeanShell sous X que vous pouvez lancer avec la commande xbsh. Celle-ci possède quelques fonctionnalités supplémentaires (en particulier un explorateur de classes). Néanmoins pour une utilisation courante, bsh est très largement suffisant ... d'autant plus que l'interface X n'est pas très agréable à utiliser.

Java strict ou pas?

Avant d'aller plus loin il est nécessaire de faire un petit point sur le rapport entre BeanShell script et le langage Java. Même si la syntaxe de ces deux langages se ressemble très fortement (c'est fait exprès), il existe tout de même des différences fondamentales:

Sans rentrer trop dans les détails cela veut dire que BeanShell acceptera des expressions qui ne sont pas des expressions Java valides. Vous en avez vu un exemple dans la section précédente avec l'instruction exit. Selon votre goût ou vos objectifs, cela peut être acceptable ou pas.

Si vous voulez que BeanShell n'accepte que des expressions Java valide, il est possible de le faire passer dans le mode Java strict:

bsh % setStrictJava(true);

Maintenant, si vous tentez de quitter en utilisant l'instruction exit précédente, vous constaterez qu'elle n'est plus acceptée:

bsh % exit();
// Error: EvalError: Error loading command: : Error loading script: Sourced file:
/bsh/commands/exit.bsh : (Strict Java mode) Assignment to undeclared variable:
exit : at Line: 3 : in file: <unknown file> : exit ( ) 

Autrement dit, pour quitter, vous devrez recourir à la même expression que pour quitter un programme Java. En clair, il faudra appeler la méthode System.exit:

bsh % System.exit(0);
sh$

Note:

Vous pouvez aussi quitter l'interpréteur en tapant ctrl-d ou ctrl-c.

Dernier point à noter, pour quitter le mode Java strict, il faut à nouveau utiliser la commande setStrictJava:

bsh % setStrictJava(false);

Euh... et comme vous remarquez, ce n'est pas une expression Java valide. Mais c'est quand même accepté par BeanShell!

Un petit exemple

Un des intérêts de BeanShell est de pouvoir facilement prendre en main une classe: créer une instance et faire quelques appels de méthodes dessus. Cela peut être utile lors de la découverte d'une nouvelle bibliothèque – ou encore, comme nous allons le voir ici, pour examiner le comportement d'une classe que vous venez d'écrire.

Pour faire simple, je vous propose la classe Serie. Celle-ci pourrait servir à modéliser une série télévisée dans une application de gestion de vidéothèque. Il s'agit d'une classe Java tout ce qu'il y a de plus ordinaire. Bien sûr, il faut la compiler (avec javac) pour que BeanShell puisse s'en servir:

sh$ cat Serie.java 
public class Serie {
    public Serie(String title, int year) { 
	this.title = title;
	this.year = year;
	this.rating = 0;
    }

    public String getTitle() { return title; }
    public String getDescription() { return String.format("%s (%d)", title, year); }

    public int	getRating() { return rating; }
    public void	setRating(int rating) { this.rating = rating; } 

    private String  title;
    private int	    year;
    private int	    rating;
};

sh$ javac Serie.java 
sh$ ls 
Serie.class  Serie.java

En lançant BeanShell, il va être possible d'utiliser cette classe dans un script. Par exemple pour en valider le fonctionnement:

sh$ bsh
BeanShell 2.0b4 - by Pat Niemeyer (pat@pat.net)
bsh % serie = new Serie("The Avengers", 1961);
bsh % print(serie.title);
The Avengers

Remarque:

Ici, je n'utilise pas le mode Java strict: vous pouvez le constater en particulier parce que:

  1. la variable serie est initialisée sans déclaration de type préalable;
  2. j'utilise une commande BeanShell (print) pour afficher le titre de la série.

Bien sûr, on peut faire la même chose en mode Java strict:

bsh % setStrictJava(true);                    
bsh % Serie serie = new Serie("The Avengers", 1961);
bsh % System.out.println(serie.title);
The Avengers

Le résultat est sensiblement le même, mais il y a un peu plus de choses à taper...

Dans BeanShell il y a bean

Si vous connaissez un peu Java, quelque-chose doit vous surprendre dans notre exemple: BeanShell semble me laisser accéder au champ title bien que celui-ci soit déclaré privé (private). Si vous êtes un peu plus familier avec Java, vous connaissez l'explication. Celle-ci se résume en un mot: JavaBeans.

Pour faire simple, JavaBeans est une spécification Java qui définit un certain nombre de règles pour faciliter l'interopérabilité des classes Java. En particulier, cette spécification précise que pour accéder à un champ il est possible d'utiliser un accesseur en lecture (getter). C'est à dire une méthode de la forme getChamp().

Toutes ces explications pour dire que quand j'écris:

bsh % print(serie.title);

BeanShell détecte que le champ title est inaccessible puisqu'il est privé. Et donc BeanShell utilise la solution de repli qui consiste à utiliser l'accesseur correspondant getTitle. Bref, tout se passe comme si l'instruction tapée avait été:

bsh % print(serie.getTitle());

Où la chose est assez élégante, c'est que l'utilisation d'un getter n'est pas conditionnée au fait que le champ correspondant existe. Vous avez-vu dans le code la méthode getDescription:

public String getDescription() { return String.format("%s (%d)", title, year); }

La classe Serie ne contient pas de champ description. Cependant, BeanShell m'autorise à écrire ceci:

bsh % print(serie.description);
The Avengers (1961)

A nouveau, comme BeanShell ne trouve par de champ description accessible (et pour cause: ce champ n'existe pas), il essaye sa solution de repli qui consiste à appeler l'accesseur. Le code exécuté est donc le même que ci-dessous:

bsh % print(serie.getDescription());
The Avengers (1961)

Bean et setters

BeanShell respecte aussi la spécification JavaBeans pour les accesseurs en modification (setter). C'est à dire que si vous essayez de modifier un champ inaccessible d'un objet, BeanShell va tenter d'utiliser le setter correspondant. Prenons l'exemple du champ rating qui permet de noter une série:

/* ... */
    public int    getRating() { return rating; }
    public void   setRating(int rating) { this.rating = rating; } 
 
/* ... */
    private int   rating;

Le champ est privé, mais la classe fournit un getter (getRating) et un setter (setRating). BeanShell nous permet donc d'écrire le code suivant:

bsh % serie.rating = 5;
bsh % print(serie.rating);
5

Mais maintenant vous devriez comprendre qu'en réalité le code exécuté est celui-ci:

bsh % serie.setRating(5);
bsh % print(serie.getRating());
5

Syntaxe Java(-like)

BeanShell vous donne aussi accès aux classes Java standards (en fait toutes celles disponibles dans le classpath). Ainsi qu'aux structures de contrôles et de données de Java.

Par exemple, si je veux, je peux créer une liste de mes séries préférées et utiliser une boucle pour les afficher:

bsh % list = new LinkedList();                  
bsh % list.add(new Serie("The Avengers", 1961));
bsh % list.add(new Serie("Star Trek", 1963));   
bsh % list.add(new Serie("The Prisoner", 1967));
bsh % for(Serie s : list) 
          print(s.description);
The Avengers (1961)
Star Trek (1963)
The Prisoner (1967)

Note:

Je profite aussi de cet exemple pour vous faire remarquer que si la syntaxe Java5 est supportée (avec la boucle for ... in), BeanShell ne supporte cependant pas la syntaxe des generics. Ainsi, le code suivant est invalide:

bsh % list = new LinkedList<Serie>();
// Error: Parser Error: Parse error at line 3, column 22.  Encountered: <
bsh % // Error: Parser Error: Parse error at line 1, column 7.  Encountered: )

Et vous pouvez aussi définir vos propres classes directement dans BeanShell. Par exemple, imaginons que nous désirons créer une classe SpinOff pour représenter une série dérivée:

bsh % class SpinOff extends Serie {
    public SpinOff(title, year, originalSerie) {
        super(title, year);
        this.originalSerie = originalSerie;
    }

    public String getDescription() {
        return super.getDescription() + " - based on " + originalSerie.getDescription();
    }

    private Serie originalSerie;
}
bsh % s = new Serie("The Avengers", 1961);
bsh % n = new SpinOff("The New Avengers", 1975, s); 
bsh % print(n.description);      
The New Avengers (1975) - based on The Avengers (1961)

Piège:

Dans la méthode getDescription, je prends bien soin d'appeler la méthode de la classe de base (super.getDescription()). A contrario de ce que nous avons vu précédemment, la version suivante ne marche pas dans ce contexte:

    public String getDescription() {
        return super.description + " - based on " + originalSerie.description;
    }

En effet, avec ma version de BeanShell (2.0b4), ce code produit le résultat suivant:

void - based on The Avengers (1961)

Autrement dit, si BeanShell prend l'initiative d'appeler le getter associé au champ description dans le cadre d'une référence à partir d'une donnée membre (originalSerie.description). Il ne sait pas faire de même lors de l'accès à un champ d'une classe de base (super.description).

Interfaces graphiques

Je ne vais pas m'éterniser trop sur cette partie. Mais que BeanShell nous permette d'accéder à n'importe quelle classe Java ouvre des possibilités amusantes. Par exemple en utilisant les classes Swing pour créer une interface graphique:

bsh % frame = new javax.swing.JFrame("BeanShel FileChooser Demo");
bsh % frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
bsh % chooser = new javax.swing.JFileChooser();
bsh % if (chooser.showOpenDialog(frame) != javax.swing.JFileChooser.CANCEL_OPTION)
bsh %    print("Votre choix: " + chooser.getSelectedFile());
bsh % else
bsh %    print("Annulé");

Remarque:

Tout comme en Java, j'aurais pu importer le paquet javax.swing (import) plutôt que d'utiliser le nom qualifié des classes JFileChooser et JFrame.

Conclusion

Voilà, notre rapide tour d'horizon de BeanShell est terminé. Pour ma part, je trouve cet outil bien pratique pour explorer les fonctionnalités d'une nouvelle bibliothèque de classes. En particulier, l'aspect interactif permet une prise en main plus intuitive que le traditionnel cycle code-compile-exécute.

BeanShell peut aussi être un outil intéressant pour s'initier au langage Java. Néanmoins, il y certains couacs dans la syntaxe qui font que le code BeanShell script et Java ne seront pas à 100% identiques. Cela induit donc un certain risque de confusion entre les deux langages. A l'inverse, la possibilité d'accéder à l'ensemble des classe de la JRE offre la possibilité d'accomplir des tâches diverses sans avoir à quitter l'environnement "script".

En conclusion, BeanShell reste un très bon outil d'appoint – et qui plus est toujours disponible. N'hésitez donc pas à aller faire un tour sur le site officiel beanshell.org pour savoir plus.

Références