Groovy et manipulation XML

Nous avons pu rencontrer Groovy et connaître plus ou moins les bienfaits de ce langage. Je vais rappeler ses principales caractéristiques et surtout vous parler de la manipulation de XML.

C’est quoi Groovy ?

  • Groovy est un langage puissant, dynamique et agile.
  • Optionnellement et dynamiquement typé.
  • La syntaxe est concise et facile à apprendre, dérivée de Java et s’inspire entre autres de Ruby, Python et Smalltalk.
  • Etant compilé en bytecode et exécuté par JVM, il s’intègre parfaitement avec Java.
  • Il apporte des simplifications et des fonctionnalités puissantes (collections, manipulation XML…).
  • Permet de faire du script, la méta-programmation et la programmation fonctionnelle.

Groovy étend Java

  • La syntaxe Groovy est un dérivé de Java. En effet, toute la syntaxe Java est compatible dans un script ou un objet Groovy.
  • Toutes les fonctionnalités de Java sont supportées par Groovy.
  • Tous les librairies Java sont réutilisables dans Groovy.
  • Une classe Groovy compilée n’est pas pas différente d’une classe Java compilée. On peut appliquer tous les principes d’objets entre les classes Groovy et les classes Java (par exemple : UneClasse définie dans UneClasse.java étend UneAutre dans UneAutre.groovy).

Closure

Une closure est un bloc code anonyme qui peut prendre des arguments, retourner une valeur et être affecté à une variable.

Syntaxe : { [argument->] instructions }

Les arguments sont optionnels et doivent être séparés par une virgule

{item}

{ -> item++ }

{ String x, int y -> println "hey ${x} the value is ${y}" }

Une closure peut être appelée comme une méthode :

def code = { 123 }   
assert code() ==  123                                
assert code.call() == 123

Une closure a toujours le paramètre implicite si aucun paramètre explicite n’est déclaré :

def  cl = {println it }
cl("Bonjour") 

Une closure est une instance de groovy.lang.Closure. Une closure peut être passé en paramètre directement ou par une variable :

[1,2,3,4,5].each { println it+1 }

def  cl = { println it+1 }
[1,2,3,4,5].each(cl) 

Une closure peut utiliser les variables et méthodes de la classe englobante :

class Point {
    def x
    def y
    private int temps() { return 4; }

    def affiche = { z -> println "($x, $y, $z, ${temps()})" }
}
  
def point = new Point(x:1, y:2)
point.affiche(3)

Meta-programming

Groovy a la capacité d’ajouter dynamiquement des méthodes aux classes à l’exécution.

Prenons un exemple de modélisation des animaux

class ScalyOrFeatheryAnimal{
  ScalyOrFeatheryAnimal layEgg(){
    return new ScalyOrFeatheryAnimal()
  }
}

class FurryAnimal{
  FurryAnimal giveBirth(){
    return new FurryAnimal()
  }
}

Pour modéliser les ornithorynques, Groovy permet de modifier dynamiquement la méthode layEgg

Platypus.metaClass.layEgg = {->
  return new FurryAnimal()
}

def baby = new Platypus().layEgg()

Un autre exemple. Groovy a ajouté des fonctions à java.lang.String. Mais on peut y encore ajouter.

String.metaClass.shout = { ->
  return delegate.toUpperCase()
}
println "Hello MetaProgramming".shout()
//sortie
HELLO METAPROGRAMMING

On peut aussi redéfinir une méthode de java.lang.String :

String.metaClass.shout = {->
  return delegate.toUpperCase()
}

String.metaClass.toUpperCase = {->
  return delegate.toLowerCase()
}

println "Hello MetaProgramming".shout()

//sortie
hello metaprogramming

Manipulation et génération de XML

Expressivité

Générer du XML avec MarkupBuilder
def sw = new StringWriter()
def html = new groovy.xml.MarkupBuilder(sw)
html.html{
  head{
    title("Links")
  }
  body{
    h1("Here are my HTML bookmarks")
    table(border:1){
      tr{
        th("what")
        th("where")
      }
      tr{
        td("Groovy Articles")
        td{
          a(href:"http://ibm.com/developerworks", "DeveloperWorks")
        }
      }
    }
  }
}

def f = new File("index.html")
f.write(sw.toString())

En sortie :

<title>Links</title>
    <h1>Here are my HTML bookmarks</h1>
    <table border='1'>
      <tr>
        <th>what</th>
        <th>where</th>
      </tr>
      <tr>
        <td>Groovy Articles</td>
        <td>
          <a href='http://ibm.com/developerworks'>DeveloperWorks</a>
        </td>
      </tr>
    </table>

Grace au principle de programmation fonctionelle par closure et MarkupBuilder, on a presque écrit du XML dans le code pour dire comment produire le XML. Ce service permet une facilité de collaboration avec les autres par exemple, l’équipe MOA.

Lecture et parsing du XML suivant
<langs type="current">
  <language>Java</language>
  <language>Groovy</language>
  <language>JavaScript</language>
</langs>

L’analyse de ce document XML trivial est incontestablement complexe en Java, il faut 30 lignes de code pour analyser un fichier XML de cinq lignes :

import org.xml.sax.SAXException;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.IOException;

public class ParseXml {
  public static void main(String[] args) {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse("src/languages.xml");

      //afficher l'attribut "type"
      Element langs = doc.getDocumentElement();
      System.out.println("type = " + langs.getAttribute("type"));

      //afficher les éléments "language"
      NodeList list = langs.getElementsByTagName("language");
      for(int i = 0 ; i < list.getLength();i++) {
        Element language = (Element) list.item(i);
        System.out.println(language.getTextContent());
      }
    }catch(ParserConfigurationException pce) {
      pce.printStackTrace();
    }catch(SAXException se) {
      se.printStackTrace();
    }catch(IOException ioe) {
      ioe.printStackTrace();
    }
  }
}

Comparez cela avec le code Groovy correspondant :

def langs = new XmlParser().parse("languages.xml")
println "type = ${langs.attribute("type")}"
langs.language.each{
  println it.text()
}

Le plus intéressant avec ce code Groovy n’est pas qu’il soit significativement plus court que son équivalent Java. Ce que j’apprécie le plus dans ce code Groovy est qu’il est de loin plus expressif. Quand j’écris langs.language.each, c’est comme si je travaillais directement avec le XML. Dans la version Java, le XML a complètement disparu. [1]

Le MarkupBuilder et la programmation fonctionnelle avec closures permettent la récursivité

Avec le MarkupBuilder et les closures de Groovy, la construction d’un objet XML se fait dans la logique fonctionnelle qui permet la récursivité. Une instance de MarkupBuilder peut être un paramètre d’une fonction récursive.

La fonction processNode dans l’exemple suivant permet de cloner un message XML avec un filtre particulier

def processNode(node, mb) { <b style="color: blue;">//recursive function</b>
  String tagname = node.name()

  def attrs = [:]
  node.attributes().each {key, value -> attrs.put(key,value) }

  if (node.children().size() > 0) {
      mb."$tagname"(attrs) {
          node.children().each {
              processNode (it, mb) <b style="color: blue;">//recursive call</b>
          }
      }
  } else {
      def value = node.text()
      def attrname = node['@name'].toString()
      if (<<<b style="color: red;">filter</b>>>)     {
          mb."$tagname"(attrs, value)
      }
  }
}
def sb = new StringWriter()
def mb = new MarkupBuilder(sb)
processNode(xml, mb)
print sb.toString()

Conclusion

Groovy s’intègre parfaitement dans un projet Java. Pour exploiter les avantages, la puissance de ce langage et ses librairies, la question se pose : pourquoi Groovy alors qu’il y a Scala, Java 8 et Clojure (les langages destinés au JVM). Je comparerai ces langages dans un autre article de blog.

[1] http://www.torrefacteurjava.fr/book/export/html/10

[2] http://www.torrefacteurjava.fr/content/m%C3%A9taprogrammation-avec-les-fermetures-expandometaclass-et-les-cat%C3%A9gories