Accueil > Java > Groovy, le nouveau langage de script de Java

Groovy, le nouveau langage de script de Java

Par Ian F. Darwin, le 29/09/2004

Traduit par Olivier, le 10/10/2004

Quand on parle de Groovy aux développeurs Java, leur première réaction, comme ce fut la mienne, est “Oh non, pas unautre langage de script pour Java”. Après tout, nous avons déjà JavaScript et Rhino, Jython, Jelly, BeanShell, JRub, Tcl/Java, Sleep, ObjectScript, Pnuts, JudoScript, Bean Scripting Framework (BSF) — qui donne accès à Perl, Tcl/TK, et d’autres — et encore d’autres. Mais d’autres développeurs attendent encore un langage de script ayant la puissance de Perl, Python ou Ruby sans avoir à tout apprendre depuis le début. Quel que soit votre camp, j’espère pouvoir vous en montrer suffisamment à propos de Groovy dans cet article, et susciter votre intérêt, pour l’essayer et juger par vous-même.

Pourquoi Groovy ?

Pourquoi Bob McWhirter et James Strachan voudraient-ils créer un nouveau langage de script ? Pourquoi le nommeraient-ils Groovy ?
Encore plus important, quelles fonctionnalités permettraient à Groovy de réussir là où d’autres se sont déjà bien implantés ?
Répondons à la troisième question en jetant un oeil aux fonctionnalités de Groovy.

Quand un langage de script autonome vient à Java, il vient avec sa propre API plutôt que d’utiliser l’API Java “standard” (pour nous).
Groovy n’est pas coupable ici –il apporte très peu d’API spécialisée ; l’ensemble de l’API Java vous est accessible depuis Groovy, et n’importe quel module externe Java est utilisable en l’ajoutant à votre CLASSPATH.

Citons la FAQ de Groovy :

Considérez Groovy comme un langage semblable à Ruby ou Python qui est étroitement intégré à la plateforme Java (au contraire des commandes shell Unix/Posix et des bibliothèques C), vous apportant la même puissance et concision de code que Ruby et Python, mais vous permettant de rester dans le JVM et de conserver vos investissements faits en J2SE, J2EE et toute la pléthore de code Java disponible sans aucune couche d’intégration ou d’API parallèle…

Les langages de script “exogène” ont tendance à avoir leur propre syntaxe, basée sur les intérêts de leurs développeurs.
Groovy est superbe ici aussi, parce que sa syntaxe est basée sur celle de Java, avec juste une cuillerée de syntaxe sucrée pour aider à faire passer la pilule.
Par exemple, au lieu d’écrire :

value = object.getItemCount();

vous pouvez simplement écrire :

value = object.itemCount

En d’autres termes, les objets en Groovy sont considérés comme des JavaBeans et les accesseurs (get/set) sont des propriétés accessibles.
Comme Groovy est, en principe, moins restrictif que Java au niveau des déclarations, la plupart des variables peuvent être initialisées sans avoir été déclarées, bien que pour le moment, il est impossible d’utiliser l’opérateur += sur un élément sans valeur.

En fait, vous pouvez créer une classe bean en Groovy sans avoir à écrire d’accesseurs, comme montré dans l’exemple person.groovy ci-dessous.
En Groovy, il n’est pas besoin d’avoir l’instruction class ni de méthodes si l’élément est autonome ; mais si vous voulez définir une ou plusieurs classes, c’est possible, et les méthodes peuvent y être définies.

Exemple 1. person.groovy

class Person {
        firstName
        lastName
        address1
        city
        province
        postcode
}

p = new Person(firstName:'Ian', lastName:'Darwin', province:'Ontario')

println "Hello ${p.firstName} ${p.lastName}"

Encore un peu de sucre syntaxique : le point-virgule est optionnel en fin d’instruction. Vous pouvez, comme moi, les écrire par habitude et parce que c’est plus consistant. Mais Groovy n’est pas Java, et si vous ne voulez pas les écrire, vous le pouvez.
Et avant que vous ne commenciez à dénigrer “le sucre syntaxique”, rappelez-vous que J2SE 1.5 (”Java 5″) offre l’auto-boxing et l’auto-unboxing, qui convertit automatiquement quand c’est nécessaire des primitives comme les ints en leur équivalent objet, comme les Integer. (Le chapitre 8 de mon livre, Java Cookbook, 2nd Edition, couvre cette fonctionnalité ainsi que d’autres fonctionnalités apportées par 1.5 tels que les Génériques ; vous pouvez télécharger ce chapitre à partir de la page de présentation du livre.)

Un troisième problème avec les langages de script sont les performances, bien que ce problème se soit amélioré avec l’augmentation de vitesse des ordinateurs.
Quelqu’un a écrit que les logiciels tendent à consommer toutes les ressources disponibles, et cela ne semble pas tout à fait faux au regard de certains systèmes d’exploitation et logiciels.
Groovy gagne encore ici, parce que les scripts Groovy peuvent être compilés en fichier .class.
Ces fichiers semblables à ceux produits par javac ou un IDE, peuvent donc profiter de l’avantage des améliorations de vitesse avec, par exemple, HotSpot, le compilateur au vol de la JVM.

En fait, semblable au niveau syntaxique, ce sont des fichiers au format bytecode de Java.
Cela ne signifie pas que vous puissiez compiler tout script Groovy en un programme Java –l’interpréteur Groovy doit se trouver dans votre classpath, pour compiler le code Java qui utilise Groovy, et pour trouver les classes Java de l’interpréteur Groovy.
Je vous montrerai un exemple plus tard.

Enfin, quelques langages de script ont tendance à avoir une documentation ambiguë, ou des implémentations multiples (Jython et Python par exemple).
Groovy est mieux placé ici aussi –son langage fait l’objet d’une Requête de Standardisation Java, ou JSR 241, qui inclut une Spécification du Langage Groovy (GLS) précise.
L’auteur et co-responsable de la spécification de la GLS est Richard Monson-Haefel, bien connu comme l’auteur du livre Enterprise JavaBeans chez Oreilly et membre du groupe d’experts de la JSR d’EJB2.
Richard est également la seule personne que je connaisse qui ait écrit un serveur EJB complet pour s’amuser, et l’a donné à la communauté open source.
( Il est également l’auteur de OpenEJB, qui a été intégré à WebObject d’Apple et Geronimo d’Apache.)

Pourquoi utiliser Groovy ?

C’est donc super de savoir que Groovy ne souffre pas des problèmes décrits plus haut, mais à quoi peut-il donc servir ?
Hé bien en fait, à toute sorte de script.
On commence à l’utiliser pour écrire des prototypes ; c’est à dire tester un algorithme en Groovy, puis le convertir en Java.
Mais vous pouvez également écrire quelque chose en Groovy et le garder comme script Groovy.
Juste pour vous donner un exemple, voyez l’exemple fixid.groovy ci-dessous, un script que j’ai écrit pour m’assurer que les fichiers Java d’un projet ont un ID CVS.

Exemple 2. fixid.groovy

# Make sure all the Java files in a collection have a CVS ID String.
# Don't apply to all files because it may damage binary files.

new java.io.File(".").eachFileRecurse({file | if (file.name.endsWith(".java")) {
   old = file.getText()
      if (!old.contains("$Id")) {
         println "Fixing file ${file.name}"
         out = file.newPrintWriter()
         out.println("// Next line added by fixid script;
                     should move into doc comment")
         out.println("// $I"+"d$")   // + to hide from CVS
         out.print(old)
         out.close()
     }
  }
})

Commençons

La seule manière d’apprendre un langage (même une variation scriptée du langage que vous connaissez) est de le télécharger et de s’y mettre. (C’est également une bonne idée de lire la page d’installation.)
Et si vous ne voulez pas taper le code de mon exemple, vous pouvez les télécharger ici.

Vous avez besoin du JDK 1.4 ou supérieur ; Groovy ne tournera pas avec 1.3.
Quand vous décompressez l’archive, vous devriez obtenir ces sous-répertoires :

  • bin : des fichiers script et des fichiers de commande pour l’exécution
  • doc : la documentation
  • lib : les fichiers .jar : groovy et asm, plus une douzaine de fichiers .jars

Pour utiliser Groovy, tout ce que vous avez à faire est d’initialiser la variable d’environnement GROOVY_HOME (et JAVA_HOME, mais c’est probablement déjà fait).
Et vous souhaitez probablement ajouter GROOVY_HOME/bin dans votre path.
Par exemple, sur Mac avec le shell Unix Korn, j’ai les lignes suivantes dans mon fichier .profile :

export JAVA_HOME=/Library/Java/Home
export GROOVY_HOME=/home/ian/groovy
PATH=$PATH:$GROOVY_HOME/bin

Il existe plusieurs manières d’invoquer Groovy de façon interactive.
L’interpréteur en ligne groovysh et l’interpréteur graphique groovyConsole vous permettent de le faire.
Pour utiliser la version courante (Beta 0.6.1) de groovysh, il suffit de taper une ou plusieurs instructions Groovy, et de taper “go” ou “execute” pour les exécuter (mais elles seront considérées comme des “commandes temporaires”).
Voici un exemple :

Exemple 3. groovysh

ian:61$ groovysh
Lets get Groovy!
================
Version: 1.0-beta-6 JVM: 1.4.2-38
Type 'exit' to terminate the shell
Type 'help' for command help

1> meaning = 42
2> println "Hello. Did you know that all it means is ${meaning}?"
3> go
Hello. Did you know that all it means is 42?

1> exit
ian:62$

Pour utiliser groovyConsole, il suffit de taper des instructions Groovy dans la panneau inférieur, puis de cliquer sur le menu Action->Run pour voir le résultat s’afficher dans le panneau supérieur.
groovyConsole a l’avantage que vous pouvez sauvegarder un script dans un fichier depuis le menu File.
Voici un exemple de groovyConsole :

De même, la commande groovy exécute un ensemble d’instructions Groovy tapées sur la ligne de commande (avec -e), ou stockées dans un fichier texte dont le nom est passé en paramètre.

groovy -e 'System.in.readLines().each { println it }'

Ces instructions permettent d’écrire un simple programme de comptage de ligne :

ls | groovy -e 'c=0; System.in.readLines().each { ++c };println c'

Au moins sur Unix/Linux/BSD/Mac OS X, vous avez besoin d’ajouter des simples quotes autour de la ligne de commande pour protéger tous les “méta-caractères” qui s’y trouvent.

Il existe également des plugins Groovy pour des IDE familiers comme Eclipse, IntelliJ, IDea, et même vim.
Je ne crois pas qu’il en existe pour Emacs ou NetBeans.
Mais vu la similarité qu’il existe entre Groovy et Java, cela ne devrait pas tarder.
Et une fois que la JSR sera un peu plus avancée, je pense que les IDE suivront.

Groovy n’est pas Java

Groovy offre des extensions de syntaxe par rapport au standard de Java.
Une des plus intéressantes est ce qu’on appelle les closures.
Ce terme a été utilisé avec différentes interprétations en informatique, mais pour le moment, voyez juste les closures de Groovy un peu comme des classes internes anonymes (anonymous inner classes).
Une des différences est que des variables peuvent être passées en entrée et sortie ; Groovy ne constitue pas une “portée lexicale” comme les inner classes.
L’exemple ci-dessous, closures.groovy, montre comment diviser une String en une List et afficher un mot par ligne, de manière “longue” et en utilisant les closures.

Exemple 4. closures.groovy

# without closures
x = "When in the course of human events ...".tokenize()
for (val in x) println val

# with closures
"When in the course of human events ...".tokenize().each { val | println val }

# with closures, default parameter name
"When in the course of human events ...".tokenize().each { println it }

Le code de la closure est utilisé pour imprimer chaque valeur de la liste.
Dans la première version, un paramètre, val, est déclaré (avant le |), et utilisé dans le corps.
Dans la seconde version, j’utilise le paramètre par défaut de Groovy désigné it (probablement le diminutif de “item”).

Une autre amélioration syntaxique intéressante est l’utilisation de variables de substitution à l’intérieur des strings.
Presque toutes les String peuvent contenir des éléments de type ${nom de variable}, et la valeur de la variable sera substituée dans la chaîne de caractères.
C’est une manière bien plus pratique que celle de Java qui utilise l’opérateur + pour la concaténation des chaînes de caractères.
Ceci peut être utilisé comme dans l’exemple hello.groovy :

Exemple 5. hello.groovy et date.groovy

i = 42
println "Hello ${i}"

Ceci affiche “Hello 42″.
La valeur substituée peut être une propriété, comme montré dans date.groovy :

i = new java.util.Date();
println "Today is day ${i.day} of month ${i.month}"

J’ai dit que Groovy n’apporte pas beaucoup de nouvelles API.
Mais il offre de nombreuses méthodes pratiques ajoutées à l’API standard.
Par exemple, la version Groovy de String ajoute tokenize() utilisée dans l’exemple closure.groovy ci-dessus.
Le fichier docs/groovy-jdk.html dans la distribution Groovy liste toutes les méthodes ajoutées aux classes JDK ; c’est une liste imposante, mais qui permet de gagner beaucoup de temps lorsqu’on utilise Groovy par rapport au “Java pur”.

Un autre exemple, la classe java.io.File de Groovy possède une méthode newReader qui renvoie un bufferReader (et newPrintWriter(), qui retourne un PrintWriter).
jaava.io.File possède également eachLine, qui ouvre le fichier et lit chaque ligne pour vous ; ça c’est de la programmation haut niveau !
L’exemple ci-dessous est wc.groovy, mon implémentation de wc, un outil traditionnel Unix qui compte les lignes, les mots et les caractères dans un fichier texte :

Exemple 6. wc.groovy

# simple implementation of Unix "wc" in groovy.
# could simplify with File.splitEachLine() but this way gets "chars" right.
filename=args[0]
chars=0; lines=0; words=0;
new java.io.File(filename).eachLine {
        chars+=it.length() + 1
        words+=it.tokenize().size();
        lines++;
}
println "\t${lines}\t${words}\t${chars}\t${filename}"

L’exécution du script produit un résultat proche de l’original :

Exemple 7. wc.groovy vs. l’original

ian:136$ wc test.txt
       2      25     130 test.txt
ian:137$ groovy wc.groovy test.txt
        2       25      130     test.txt
ian:138$

Groovy ajoute également de nombreuses méthodes très pratiques pour les listes et dictionnaires ; certaines sont montrées dans l’exemple lists.groovy ci-dessous :

Exemple 8. lists.groovy

# List operations
list = ["ian","brian","brain"]
println list;
println "Maximum value: ${list.max()}"

# inject is like an iterator with carryover of last value
list.inject("foo", { val, elem|println "${val} -- ${elem}"; return elem })
# findAll returns all the elements for which the closure returns true.
println "findAll: "+list.findAll({it.contains("ian")})

# Ruby-style ranges, +- operators, etc.
validCardYears = 2004..2008
validCardYears.each {println "Valid year: ${it}"}
lastValidCardYear = validCardYears[-1]
println "Last valid year is currently ${lastValidCardYear}"

L’exécution de ce script produit la sortie suivante :

groovy src/lists.groovy
[ian, brian, brain]
Maximum value: ian
foo -- ian
ian -- brian
brian -- brain
findAll: [ian, brian]
Valid year: 2004
Valid year: 2005
Valid year: 2006
Valid year: 2007
Valid year: 2008
Last valid year is currently 2008

Enfin, juste pour montrer qu’on peut le faire, j’ai converti wc.groovy en Java ; l’exemple WC.java donne la même réponse, mais avec environ deux fois plus de code.

Exemple 9. WC.java

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.StringTokenizer;

/** simple implementation of unix "wc" in Java. */
public class WC {
        public static void main(String[] args) throws IOException {
                int chars=0, lines=0, words=0;
                String filename=args[0];
                BufferedReader r = new BufferedReader(new FileReader(filename));
                String it;
                while ((it = r.readLine()) != null) {
                        chars+=it.length() + 1;
                        words+=new StringTokenizer(it).countTokens();
                        lines++;
                }
                System.out.println("\t" + lines + "\t" + words +
                        "\t" + chars + "\t" + filename);
        }
}

XML, Swing, Builder et tout le reste

Groovy inclut un générateur de tags de structure arborescent qui peut être sous-classé pour créer toute une variété de représentations objet de structure arborescente, incluant des tags XML, tags HTML, déclencheur d’évènements SAX, et même des interfaces utilisateur en Swing !
L’exemple ci-dessous, swingbuilder.groovy, montre une interface utilisateur avec une variété de labels et de champs texte construits depuis une liste, un pop-up “About”, et deux façons de sortir : par un bouton ou par la fenêtre principale (car defaultCloseOperation est affecté à EXIT_ON_CLOSE).

Exemple 10. swingbuilder.groovy

import groovy.swing.SwingBuilder;

theMap = ["color":"green", "object":"pencil", "location":"home"];

// create a JFrame with a label and text field for each key in the Map
swing = new SwingBuilder();
frame = swing.frame(title:'A Groovy Swing', location:[240,240],
        defaultCloseOperation:javax.swing.WindowConstants.EXIT_ON_CLOSE) {
        panel() {
                for (entry in theMap) {
                        label(text:entry.key)
                        textField(text:entry.value)
                }
                button(text:'About', actionPerformed:{
                        pane = swing.optionPane(message:'SwingBuilder Demo v0.0')
                        dialog = pane.createDialog(null, 'About')
                        dialog.show()
                })
                button(text:'Quit', actionPerformed:{ System.exit(0) });
        }
}
frame.pack();
frame.show();

Il n’y a, pour l’instant, aucune action sur les champs texte, alors, les modifier ne fera absolument rien.
Une démonstration Swing plus élaborée, et d’autres démonstrations se trouvent sur le site web de Groovy.

Voici à quoi cela ressemble en action :

Utilisation avec les servlets

Une autre API intéressante supportée par Groovy est celle des servlets de J2EE. Comme les JSPs, les GroovlyServlet fournissent un objet out qui est utilisé ici. L’exemple handleform.groovy ci-dessous est une simple GroovyServlet en action qui reçoit le contenu d’une forme HTML simple (avec nom, adresse, téléphone,…etc…) et stocke le contenu des champs dans un JavaBean, qui est ensuite sauvegardé en utilisant un accesseur de donnée déjàs écrit.

Exemple 11. handleform.groovy

import java.util.Date
import beans.Person
import beans.PersonDAO

bean = new Person();

bean.firstName = ${firstName}
// etc for other properties

out.println(<<<EOS
<html>
<head>
	<title>Thanks for Signing Up Via the Groovy Servlet</title>
</head>
<body>
The Groovy Servlet thanks you,
${firstName} at ${request.remoteHost},
for signing up with us at ${new Date()}.
<hr/>
</body>
</html>
EOS)
out.println("Bean = " + bean);
new PersonDAO().insert(bean);
out.println("<hr>");
out.println("(database updated)");

Cet exemple nous montre l’utilisation de here document (”voici un document”), une idée empruntée au Shell Bourne et à ses nombreux dérivés sur Unix.
Tout ce qui se trouve entre <<< et le marqueur de fin de ligne EOS est copié, dans ce cas, dans l’argument de out.println(), qui retourne tout le code HTML –après substitution des variables telles que ${firstname}– au navigteur de l’utilisateur.

Les GroovyServlet ne prétendent pas concurrencer des frameworks complet comme Struts ou JSF, mais c’est pratique pour quelques tâches de servlets.
Pour que ce script puisse fonctionner, il suffit d’avoir les éléments servlet et servlet-mapping dans le fichier web.xml, et deux fichiers .jar, groovy.jar et asm.jar, se trouvant dans la distribution.

Pour en savoir plus, lisez la page des Servlets Groovy

Solutions hybrides

Comment utiliser Groovy à partir de Java ? Il existe plusieurs solutions.

Vous pouvez utiliser groovyc tout comme javac pour produire des fichiers bytecode.
Imaginez que j’ai écrit ce script Groovy très complexe, g.groovy :

Exemple 12 g.groovy

i = 42
println "Hello ${i}"

La commande pour exécuter ce code comme un script est groovy g.groovy, qui produit le résultat suivant :

Hello 42

Maintenant, pour le compiler en bytecode, je tape juste groovyc g.groovy.
Puis je l’appelle depuis une classe Java. Par exemple :

public class j {
        public static void main(String args[]) {
                new g().run();
        }
}

Puis, je compile et exécute la classe j en utilisant javac et java, en n’oubliant pas d’inclure le .jar de Groovy et son fichier associé asm.jar dans mon classpath (je sais qu’il existe de meilleures façons de faire, mais j’ai choisi celui qui est le plus transparent pour les lecteurs qui veulent le tester) :

$ javac -classpath /home/ian/groovy/lib/groovy-1.0-beta-6.jar:/home/ian/groovy/lib/asm-1.4.1.jar:. j.java
$ java -classpath /home/ian/groovy/lib/groovy-1.0-beta-6.jar:/home/ian/groovy/lib/asm-1.4.1.jar:. j
Hello 42
$

Et ensuite ?

Il y a beaucoup plus de choses à dire sur Groovy que ce que je peux écrire dans cet article.
Si vous êtes un développeur Java et vous n’êtes pas encore un fan de Jython PNuts ou tout autre langage de script Java, je vous encourage à télécharger rapidement Groovy et à l’essayer.
Même si vous préférez un autre langage de script, jetez un oeil à Groovy — vous pourriez l’apprécier.

Parce que Groovy est récent, il y a encore beaucoup à faire. Les développeurs Groovy recherchent toujours de l’aide.

Groovy doit mieux faire concernant la gestion des erreurs. L’interpréteur groovysh affiche une exception attrapée et l’interpréteur de Groovy affiche la pile d’erreur (stack trace). Mais la plupart des exceptions sont dûes à des erreurs utilisateurs, et il serait bien que Groovy puisse faire le rapport de ses erreurs d’une façon plus élégante.

Au fur et à mesure que Groovy s’améliore, attendez-vous à en entendre parler de plus en plus sur les site Java, et à voir plus de développeurs l’utiliser pour des tâches de scripts.

Textes originaux en anglais sur O’Reilly : Groovy, Java’s New Scripting Language par Ian F. Darwin

opoppon Java , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
S'abonner aux commentaires de cet article