Groovy everywhere

Ausgewählte Groovy-Beispiele

Dierk König

Mit dem gesammelten Wissen vorangegangener Artikel der Groovy-Serie im Java Magazin im Rucksack machen wir uns nun daran, kleine Applikationen zu bauen, die durch geschickte Kombination der Groovy-Sprachmerkmale überraschend einfache Lösungen aufzeigen. In unser Blickfeld fallen hier eine kleine Aufgabe zur Textanalyse, ein Rich-Client-Bildbetrachter für flickr und einen SOAP-Client und -Server mit Groovy.

Ein häufiges Anwendungsfeld für Script-Sprachen ist die Analyse von Text, und weil Groovy unter anderem auch als Script-Sprache einsetzbar ist, kann man erwarten, dass sich Groovy bei dieser Art von Anwendung als nützliche Erweiterung von Java beweist.

Stellen wir uns vor, wir wollten eine Fremdsprache erlernen und die wichtigsten Vokabeln auswendig lernen. Am wichtigsten sind sicher die Worte, die in einem Text am häufigsten vorkommen. Diese wollen wir mithilfe eines kleinen Groovy-Scripts ermitteln.

Der Einfachheit halber sei unser Text wie in Listing 1 in einem String gespeichert. Wer meint, das sei unrealistisch und man sollte solche Information lieber aus einem File lesen, kann die erste Zeile durch def text = new File(‚data.txt‘).text ersetzen. So einfach ist das in Groovy.

def text = 
"""
It takes two to program, two to program,
till you really get the feeling for your test case,
two to program, two to program,
through the bands of code.
"""

def words = text.split(/W/).grep(~/w+/)
def frequencies = [:]

words.each { word ->
    frequencies[word] = frequencies.get(word,0) + 1
}
def wordList = frequencies.keySet().toList()
wordList.sort { frequencies[it] }                  

def statistic = "n"
wordList[-1..-4].each { word ->
    statistic += word.padLeft(8)   + ': '
    statistic += frequencies[word] + "n"
}

assert statistic ==
"""
 program: 4
     two: 4
      to: 4
     the: 2
"""

Als Text dient uns eine Abwandlung des Klassikers „It takes two to tango“. Die vier häufigsten Worte in diesem Text lassen sich wie folgt in einer Statistik zusammenfassen:

assert statistic ==
"""
 program: 4
     two: 4
      to: 4
     the: 2
"""

Diese Groovy Assertion dient uns bei der Entwicklung als Unit-Test, der direkt Teil unseres Programms ist, was man auch als „Inline-Unit-Test“ bezeichnet.

In typischer XP-Manier gehen wir rückwärts vor und überlegen uns, wie wir solch einen String erzeugen könnten. Angenommen, wir hätten eine nach Häufigkeit sortierte Wortliste und die Häufigkeit jedes Worts in einer Map gespeichert, dann könnten wir die letzen vier Worte in der Liste so in die Statistik einfließen lassen.

def statistic = "n"
wordList[-1..-4].each { word ->
    statistic += word.padLeft(8)   + ': '
    statistic += frequencies[word] + "n"
}

Interessant sind hier die negativen Indizes, um das letzte respektive das viertletzte Element zu lokalisieren. Der damit beschriebene Range ist implizit „umgekehrt“ (reversed), und die Subliste der letzen vier Elemente wird daher in umgekehrter Reihenfolge von each durchlaufen.

Die initiale Wortliste zu erzeugen ist ein Einzeiler. Wir spalten den Text an allen Nicht-Wort-Tokens (W), und aus der resultierenden Liste behalten wir nur die Einträge, die ausschließlich aus Wort-Tokens bestehen und länger als ein Token sind (w+). Die Map der Häufigkeiten ist initial leer und wird gefüllt, indem über alle Worte in der Liste iteriert und der jeweilige Wert in der Map inkrementiert wird. Bei der Ermittlung des Werts sorgt die get(word,0)-Methode dafür, dass unbekannte Worte die Häufigkeit 0 haben. Zu guter Letzt sortieren wir die Worte nach der ermittelten Häufigkeit.

def words = text.split(/W/).grep(~/w+/)

def frequencies = [:]
words.each { word ->
    frequencies[word] = frequencies.get(word,0) + 1
}
def wordList = frequencies.keySet().toList()
wordList.sort { frequencies[it] }                  

Die sort-Methode ist erstaunlich flexibel. Sie wird optional mit einer Closure parametrisiert, die beim Sortieren für den Elementvergleich herangezogen wird. Das kann auf zwei Arten geschehen, je nachdem, ob die Closure ein oder zwei Parameter deklariert. Bei einem Parameter (in unserem Fall implizit der it-Parameter) wird die Closure auf beide zu vergleichenden Elemente angewendet, und die Resultate werden verglichen. Bei zwei Parametern werden beide Elemente an die Closure übergeben und diese wirkt als Comparator, z.B. list.sort{a,b-> ab}. In unserem Fall können wir damit sehr leicht die Sortierlogik auf Inhalte der Häufigkeits-Map abstützen, obwohl wir die Wortliste, also eine ganz andere Datenstruktur, sortieren.

Eine vergleichbare Implementierung in reinem Java ist massiv aufwendiger. Groovy unterstützt uns hier durch die leichte Verwendbarkeit von regulären Ausdrücken (split, grep), den leichtgewichtigen Umgang mit den Datentypen String, List, und Map sowie den zusätzlichen Methoden (get, sort) aus dem GDK. Groovy ist aber beileibe nicht auf textlastige Applikationen beschränkt. Man kann auch sehr leicht clientseitige GUIs bauen und mit Fern-Datenbeständen verbinden.

Rich Client für flickr

Als Nächstes bauen wir einen Bildbetrachter für die heute interessantesten Bilder bei flickr [1]. flickr hat seine eigene Meinung davon, welche Bilder interessant sind, und wir werden uns dieser Meinung anschließen. Wer sich bei flickr registriert, kann mithilfe seines persönlichen Schlüssels die URLs der interessantesten Bilder mittels eines REST-Services ermitteln. Diese wollen wir mithilfe eines minimalen Swing User Interface anzeigen, und zwar so, dass nach einem Klick auf das Bild das nächste Bild angezeigt wird. Abbildung 1 gibt einen Eindruck, wie dieses Interface aussehen kann. Sie werden natürlich andere Bilder sehen, denn diese ändern sich jeden Tag.

import groovy.swing.SwingBuilder
import javax.swing.*

key     = 'KEY HIER EINTRAGEN'
counter = 1

def updateButton(button) {
    counter++
    def apiUrl = "http://www.flickr.com/services/rest/?" +
       "method=flickr.interestingness.getList&per_page=1&" +
       "page=$counter&api_key=$key"
    def rsp      = new XmlParser().parse(apiUrl)
    def photo    = rsp.photos.photo[0]
    def imageUrl = "http://static.flickr.com/" + 
       "${photo.'@server'}/${photo.'@id'}_${photo.'@secret'}_m.jpg"
    button.icon  = new ImageIcon(imageUrl.toURL())
    button.text  = photo.'@title'
    return button
}

def frame = new SwingBuilder().frame(
        title: 'Groovy Flickr Viewer',
        defaultCloseOperation: WindowConstants.EXIT_ON_CLOSE) {
    updateButton( button (
        horizontalTextPosition: SwingConstants.CENTER,
        verticalTextPosition:   SwingConstants.BOTTOM,
        actionPerformed: { event -> updateButton(event.source) }
    ))
}
frame.pack()                               
frame.visible = true

Abb.1: Groovy Flickr Viewer

Listing 1 enthält die vollständige Lösung. Fangen wir mit der Beschreibung des Swing GUI an: Groovys SwingBuilder erlaubt eine sehr einfache Beschreibung eines Frame-Objekts, das genau einen Button enthält. Das Gerüst sieht so aus:

new SwingBuilder().frame {
    button()
}

Damit das Ganze etwas ansprechender aussieht, werden die Builder-Methoden frame und button noch mit Argumenten für Titel, Positionierung etc. aufgerufen [2]. Speziell ist das Attribut actionPerformed. Es wird mit einer Closure initialisiert, die fortan als ActionListener für diesen Button registriert ist. Sie ruft die updateButton-Methode auf und übergibt den Event Source, also eine Referenz, auf den Button selbst. Das Event-Objekt wird beim Auslösen des Events an die Closure geschickt.

Die Methode updateButton setzt das Icon des Buttons auf das aktuelle flickr-Bild und den Button-Text auf den Text des Bildes. Das ist bei der initialen Darstellung des GUI zu tun und anschließend bei jedem Klick auf den Button. Damit ist das User Interface fertig.

Die Beschaffung der Bild-Informationen über den REST-Service ist denkbar einfach. Wir arbeiten mit jeweils nur einem Bild pro Seite, und damit wird die Seitennummer zur Bildnummer. Beides lässt sich in dem URL als Parameter angeben. Groovys XML Parser übernimmt den Verbindungsaufbau, das Empfangen des Ergebnisses und natürlich das Zerlegen in Objekte. Mithilfe eines GPath-Ausdrucks lokalisieren wir das erste (und einzige) photo/>-Element. Von diesem benötigen wir die Attribute server, id, secret und title, um den effektiven Bild-URL zu ermitteln und den Button entsprechend zu befüllen.

Damit ist unsere zweite kleine Applikation fertig. Sie kombiniert verschiedene Aspekte der Groovy-Sprache und Laufzeitumgebung zu einer idiomatischen Lösung: das Builder Pattern, GStrings, um die URLs zu bauen, Groovy Beans inklusive Event Handling, XML plus Remoting und GPath sowie die Verwendung bestehender Java-Funktionalität (ImageIcon), und das Ganze als ausführbares Script, das keiner vorherigen Kompilation bedarf.

Groovy SOAP

REST-Services sind schön und gut und gerade in der dynamischen Welt der Script-Sprachen oftmals eine angemessene Lösung. Im Bereich der kommerziellen Softwareentwicklung sind typorientierte Protokolle allerdings stärker verbreitet, mit SOAP als dem bekanntesten Vertreter.

Groovy ist für die Arbeit mit SOAP-Services bestens geeignet, sowohl auf der Client- wie auch auf der Serverseite, und erleichtert nicht nur das Testen und Prototyping von Applikationen, sondern ist auch reif für den produktiven Einsatz. Schon mit den üblichen „Bordmitteln“ von Groovy kann man gut mit Web Services arbeiten [4], noch eleganter wird das Ganze jedoch mit dem Einsatz des GSOAP-Moduls, das man auf der Groovy-Website findet [3].

Mithilfe dieses Moduls kann man von Groovy aus sehr einfach einen Web Service aufrufen. Das momentane Wetter in Hamburg kann man z.B. so anfragen:

import groovy.net.soap.SoapClient

def url = 'http://www.webservicex.net/WeatherForecast.asmx?WSDL'
def proxy = new SoapClient(url)
def result = proxy.GetWeatherByPlaceName('Hamburg')

println result.latitude
println result.details.weatherData[0].weatherImage

und bekommt das Ergebnis
40.5487976
http://www.nws.noaa.gov/weather/images/fcicons/fg.jpg

Einen SOAP-Service auf Serverseite bereitzustellen ist genauso einfach. In Groovy braucht man dafür keinerlei Definitionen bereitzustellen. Diese werden automatisch erzeugt. Es reicht, die Serviceimplementierung als einfaches Script anzulegen, zum Beispiel für einen Service, der Rechenoperationen zur Verfügung stellt, in etwa so:

double add(double op1, double op2) {
    return (op1 + op2)
}

double square(double op1) {
    return (op1 * op1)
}

Man braucht noch nicht einmal eine Klassendefintion und muss dieses Script auch nicht kompilieren. Es ist ausreichend, diesen Script-Code in der Datei MathService.groovy zu speichern, damit sie vom Server wie folgt verwendet werden kann:

import groovy.net.soap.SoapServer
def server = new SoapServer("localhost", 6990)
server.setNode("MathService")
System.out.println("start Math Server")
server.start()

Schneller und einfacher ist der Umgang mit SOAP kaum denkbar. GSOAP ist darüber hinaus ein Beispiel dafür, wie leicht man mit Groovy ein extrem einfaches API über zugrunde liegende Fremdkomponenten legen kann. GSOAP basiert auf XFire, ist aber deutlich einfacher zu verwenden. Analoges gilt für Groovys Scriptom Modul, dass Visual Basic-ähnlichen Zugriff auf Windows-Komponenten erlaubt und auf der Java-COM Bridge (jacob) basiert. In all diesen Fällen ist es das Groovy-Meta-Objekt-Protokoll, mit dessen Hilfe am API Methoden dynamisch verfügbar gemacht werden.

… und so geht’s weiter

Groovy ermöglicht an vielen Stellen einen einfachen und direkten Zugang zu bestehenden Java-Funktionalitäten. Dies gilt vor allem auch für Javas reichhaltige Unterstützung für Webapplikationen, das Spring Framework und OR Mapping z.B. mit Hibernate.

Wir haben hier drei Anwendungsbeispiele von Groovy gesehen, aber natürlich gibt es eine unerschöpfliche Menge weiterer Anwendungen. Groovy ist an keine Anwendungsdomäne gebunden und eignet sich für alle Arten dynamischer Programmierung. Viel Spaß beim Einsatz von Groovy und dem Erforschen neuer und spannender Einsatzgebiete!

Dierk König: Dynamischer Nachwuchs. Dynamische Programmierung auf der Java-Plattform, in Java Magazin 8.2006

Dierk König et al.: Groovy in Action, Manning, 2006

Geschrieben von
Dierk König
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.