Montag, 17. September 2012

Groovy: Script mit Bibliotheken verknüpfen: (at)Grap


Ein Script hat den Charme das es klein und handlich weitergegeben werden kann. Doch das hat schnell ein Ende, wenn man auf 3rd Party Bibliotheken zugreifen muss. Doch auch dafür hat groovy eine Lösung: Die Annotation @Grab. Die Annotation gibt der Laufzeitumgebung die Information welche Jars aus Maven Repositories geladen werden sollen, bevor das Script ausgeführt werden kann.
Hier nur kurz das offizielle Beispiel direkt zitiert:
@Grab('commons-lang:commons-lang:2.4')
import org.apache.commons.lang.WordUtils
println "Hello ${WordUtils.capitalize('world')}"

Die Synthax im Beispiel ist die Kurzschreibweise. Es ist auch eine etwas textlastigere Variante möglich. Diese liefert dann auch direkt die Erklärung was die einzelnen Elemente bedeuten:
@Grab(group='org.apache.solr', module='solr-commons-csv', version='3.5.0') 

Freitag, 14. September 2012

Spring Roo: sortierte Listen

Um mit Spring Roo per Konsolenbefehle sortierten Listen zu erstellen, erfordert es einen kleinen manuellen Eingriff.
Hier eine Anleitung anhand eines Beispiels:

field set  --fieldName orderedBs --type ~.domain.ClassB 
--cardinality ONE_TO_MANY --class ~.domain.ClassA
field number --fieldName orderNo --type java.lang.Integer  --min 0

Damit erhält man ein unsortiertes Set generiert, es gibt aber kein Schlüsselwort um eine sortierte Liste zu erzeugen. Alle Attribute für die Sortierung sind aber bereits angelegt.
Das Gute: Die Views und Controller  funktionieren jetzt schon ohne Anpassung für sortierte Listen. 
Die einzige Änderung die man nun per Hand machen muss, ist die Umstellung in der generierten Modell - Klasse (~domain.ClassA):  

    @OneToMany(mappedBy = "classA", cascade = CascadeType.ALL)
    @OrderBy("orderNo")
    private List<ClassB>  orderedBs  = new LinkedList<ClassB>();

Das war es, es werden sortierte Ergebnisse in den Views ohne weitere Anpassungen geliefert.
Kleiner Pferdefuß: da "order" ein Schlüsselwort in SQL ist, kann es nicht als Attributname für die Annotation OrderBy verwendet werden. D.h. @OrderBy("order") ist durch SQL verboten! Das merkt aber erst die Datenbank, die Annotation, Hibernate und Spring halten sich hier völlig ruhig.

Donnerstag, 13. September 2012

Groovy: Mit Files und Texten effizient arbeiten

Mit groovy kann man sehr schon mit Files und Texten arbeiten. Aber es gibt auch ein paar Stolpersteine. Damit man sicher zum Ziel kommt, hier ein kleines Tutorial.
In diesem Script wird eine Datei geladen und die doppelten Zeilen gelöscht, sowie der Inhalt sortiert. Das Ergebnis wird dann in eine andere Datei mit gleichem Namen aber einer anderen Endung (.txt) gespeichert.
fName = "xyz.csv"
infile = new File(fName)
outfile = new File(fName[0..-4]+"txt")
erg = new HashSet()
infile.eachLine { erg.add(it) }
outfile.write('');
erg.sort().each{
    outfile.append(it+"\n", "UTF-8");
}

java.io und java.util sind schon drin
Das Script ist vollständig. Auch ohne imports. Der Grund: Groovy hat in groovy.lang die Pakete io und util dabei. Eine Kleinigkeit, aber für Java Umsteiger doch bemerkenswert.

File ist nicht gleich java.io.File
Eigentlich doch, nur das groovy die Klasse File erweitert hat um einige Convience Funktionen. Im Beispiel sieht man z.B. eachLine was eine Closure erwartet, die dann auf jede Zeile angewendet wird.  Was in Java lästig ist: Das recht alte java.io Paket wirft mit Exceptions um sich. Gerade für ein Script ist das lästig. Da hat groovy ganz einfach dafür gesorgt, dass man sich darum nicht kümmern muss. Obwohl es in groovy auch einen sehr guten try catch Mechanismus gibt, ist per default kein Exception Handling nötig. Zu bemerken ist noch, das ein explizites close auf die Dateien hier nicht notwendig ist, da groovy Files automatisch frei gibt.

Strings sind auch Arrays und Arrays sind anders 
Viele Java Programmierer werden bei der 3. Zeile stocken. Denn in groovy kann ein String als Array angesehen werden. D.h. man kann die Buchstaben im String einfach per Array Index adressieren. Damit nicht genug, die letzte Stelle in einem Array ist die -1. D.h wenn man den 3 Buchstaben des Datei Suffix adressieren will, findet man den auf der Position -4. Jetzt kommt noch eine weitere Erweiterung von groovy ins Spiel, das bilden von Subarrays. Dazu kann man einfach wie im Beispiel [0..-4] schreiben, um den Substring "alles außer die letzten 3 Zeichen" zu extrahieren.

Encoding raten und die Nebeneffekte
Ein wichtiger Designentscheid von Groovy ist Convience: Code less. Nur in selten Fällen kann es in die andere Richtung gehen. Das CharsetToolkit ist so ein Kandidat. Es wird z.B. beim lesen einer Datei das encoding geraten in dem die ersten Zeilen gelesen werden und daraus ein Charset ermittelt wird. Wenn das nicht geht, z.B. bei Ausgabefiles wird der System Default Charset verwendet. Da in meinem Beispiel aber der Text im inFile UTF-8 codiert ist, wird er entsprechend auch so in der Ausgabe erwartet. Darum muss das Charset, wie oben beschrieben, explizit angegeben werden.

Strings und Charakter sind eins, nur für Java nicht
Auch und obwohl ich es gut finde, das groovy nicht zwischen chars und Strings unterscheidet, gibt es beim Wechsel von groovy code zu Java Bibliotheken einen Stolperstein. Wird als Parameter einer Java Methode ein char erwartet, dann findet groovy die Methode nicht. Die Folge ist, das man groovy ein wenig auf die Sprünge helfen muss. Kleines Beispiel gefällig:


import org.apache.commons.csv.*

// ok
new CSVParser(new FileReader(new File(fName)),
       new CSVStrategy(';' as char,'"' as char,'#' as char))

// geht nicht
new CSVParser(new FileReader(new File(fName)),
       new CSVStrategy(';','"','#' ))

Wenn man es weiss, ist es erklärlich. Bis man es gefunden hat, kann  man schon an sich Zweifeln...

Mittwoch, 12. September 2012

Groovy: Die Closure - Beispiel findAllFiles

Kernkonzept: Die groovy Closure
Ich mag groovy für kleine Aufgaben, die in Java zumeist unhandlich sind. Nichts gegen Java, aber manches ist gescripted handlicher oder macht zumindest mehr Spass. Einsatzszenarien sind bei mir in der Regel so definiert:

  • kaum Performance Anforderungen. Wenns um die letzten Prozente geht, muss es eben Java sein. Das bedeutet nicht, das in Performance kritischen Anwendungen kein groovy drin sein kann, nur eben nicht an den kritischen Sequenzen.
  • String Manipulation als Kernaufgabe. Ein Script ist ideal, wenn es darum geht automatische Textverarbeitung zu machen. Ob es klassisches Search/Replace oder Textgenerierung/Konvertierung ist, es ist mit groovy leichter. 
  • Klein genug um wartbar zu sein. Jedes einzelne Script sollte nicht mehr als eine A4 Seite lang sein. Da groovy sehr kompakt ist, passt auch viel Logik in wenig Zeilen. Darum ist Modularisierung extrem wichtig, sonst landet man schnell bei "write only" Programmen. 


Damit groovy Spaß macht, braucht man etwas Handwerkszeug um richtig loszulegen. Eines der wichtigsten: Die Closure.

Die Sourcen, an denen ich entlang hangeln werde (am Ende des Postings ist der gleiche Quellcode ohne Zeilennummern als Kopiervorlage):


1: findAllFiles = { File folder, Closure fileMatch ->
2:     def fileNameFilter = {dir, name -> fileMatch(name)} as FilenameFilter
3:     def dirFilter = {File file -> !file.isFile()} as FileFilter
4:     def files = []
5:     def innerFindAll
6:     innerFindAll = {File lFolder ->
7:         if(!lFolder.isFile()){
8:             lFolder.listFiles(dirFilter).each{ innerFindAll(it)}
9:             files.addAll(lFolder.listFiles(fileNameFilter ))
10:         }
11:         return files
12:     }
13:     return innerFindAll(folder)
14: }


Und so wirds benutzt:


println findAllFiles(new File("."), {it.endsWith(".xml")})  

Das Ergebnis ist ein kleines Helferlein, ich verwende es etwa um in allen gelieferten xml Dateien Platzhalter für Autor, Erzeugungsdatum und Version einheitlich zu setzen. Aber das ist ein anderes Thema. Zuerst mal sind die Grundlagen für die Closure zu beschreiben.

Eine erste Closure
Der Code definiert eine Closure findAllFiles. Eine Closure ist ein first Class Bewohner des groovy Ökosystems. Formal ist eine Closure in den groovy Docs definiert, informell ist es ein Methoden-Pointer und das erklärt es für mich auch ausreichend um es benutzen zu können. Etwas ungewohnt für Java Nutzer ist die Definition der Parameter. Dazu hat auch die offizielle Dokumentation eine gute Beschreibung. Von Interesse ist der zweite Parameter: Closure fileMatch. Hiermit wird das Einsatzgebiet flexibler. Die Closure findAllFiles bekommt auf diesen Weg mitgeteilt nach welchen Kriterien die Dateien gefiltert werden sollen. Es ist auch beim Aufruf gut lesbar, was die Closure macht, im Beispielaufruf: finde alle Dateien die mit .xml aufhören. Ich denke, die Aufrufzeile kommt gut ohne Kommentar aus.
Eine weitere Info zu Parametern: Es ist in groovy nicht notwendig, dass man Variablen explizit definiert, ich tue es aber immer, wenn es der Lesbarkeit dient. Es ist z.B. die Variable folder besser dokumentiert, wenn ich den Typ (java.io.File) aufführe. Woher soll der Nutzer sonst wissen, ob ich den Folder als File oder als String erwarte? Alternativ kann man die Variable entsprechen benennen, in der Art fileFolder, nach kurzer Überlegung, ist es aber keine gute Alternative. Ich müsste ja überall den sperrigen Namen tippen und so eindeutig wie eine explizite Deklaration ist es immer noch nicht.

Closure um Interfaces zu implementieren
Die nächsten zwei interessanten Closures sind fileNameFilter und dirFilter. Es ist eine besondere Nutzung, in dem hier jeweils das Interface java.io.FilenameFilter bzw. java.io.FileFilter implementiert wird. Das funktioniert so einfach für Interfaces mit genau einer Methode. Für Interfaces mit vielen Methoden gibt es in groovy auch viele Möglichkeiten, ich verwende dann aber ausschliesslich die original Java Art ein Interface zu implementieren. Die speziellen groovy Spielarten verschlechtern IMHO die Lesbarkeit ohne dafür echten Mehrwert zu bieten. Findet eure bevorzugte Methode am besten selbst raus, nur alle durcheinander zu benutzen erschwert die Lesbarkeit enorm.

Closures und Rekursion
Mit Closures können Rekursionen, fast so wie in Methoden programmiert werden. Aber nur fast: Es muss zuerst die Closure definiert werden, dann kann sie in der Implementierung selbst referenziert und damit rekursiv benutzt werden. Darum erstreckt sich die Definition der Closure innerFindAll über zwei Zeilen (Zeile 5+6).

Implizites Return
Die Zeile 13 return innerFindAll(folder) kann auf das Schlüsselwort return verzichten. Der letzte Wert in einem Codeblock wird in groovy immer zurückgeliefert. Ich finde aber, dass ein explizites return das lesen erleichtert.

Unbenannte Closure
Die letzte Closure im Codebeispiel ist eine unnamed Closure. Es ist die Sequenz: {it.endsWith(".xml")} Die geschweiften Klammern dienen dazu, eine Closure zu definieren. Da die Closure hier als Aufrufparameter genutzt wird und danach obsolet ist, kann sie unnamed definiert werden.

Unbenannte Variable it
Wer kennt nicht Cousin It? Es gibt auch ein interessantes "Es" in groovy. Jede Closure hat implizit it als Variable und kann ohne zutun benutzt werden. Es spart einiges an Codierarbeit, aber ich nutze es nur in Closures die nicht an verschiedenen Stellen aufgerufen werden, da mit der Automatik auch das Mittel der Dokumentation flöten geht. Als Faustregel: In unbenannten Closures ist it ok, ansonsten ist ein Namen vorzuziehen, sorry Cousin It.

Lesekurs für Java Programmierer
Es ist für Java Programmierer, die wohl das Gros der groovy Nutzer ausmachen, der schwierige Teil, aufmerksam die Klammern zu lesen. Die {} haben  in groovy wie zuvor beschrieben, eine neue Wertigkeit. Die Zeile 8 definiert z.B. die Closure each, es wird schnell mal () verwendet, was einem Methodenaufruf entspricht. 
Achtung auch bei []. Es ist keine Array Definition, sondern die Definition einer Liste. So auch im Beispiel auf Zeile 4.
Die resultierenden Fehlermeldungen deuten nicht immer direkt auf die Ursache.
Es ist eine systematische Schwäche von Scriptsprachen, da es nicht zu so deutlichen Fehlermeldungen kommt wie etwa in Java. Aber durchhalten, nach kurzer Zeit hat man die Sematiken der Fehlermeldungen verstanden und ist so schnell mit der Korrektur wie in Java.


Keine Semikolons wo es geht
Es ist dort wo ein Zeilenumbruch vorkommt, nur sehr selten nötig Semikolons zu setzen. Wenn man beim Umstieg auf groovy Konsequent die Semi's weglässt, schränkt man sich kaum ein, zwingt sich aber das man ein Mindestmaß an guter Formatierung beibehält.

Sourcecode
Der Quellcode als Kopiervorlage, d.h. ohne die Zeilennummern.

findAllFiles = { File folder, Closure fileMatch ->
     def fileNameFilter = {dir, name -> fileMatch(name)} as FilenameFilter
     def dirFilter = {File file -> !file.isFile()} as FileFilter
     def files = []
     def innerFindAll
     innerFindAll = {File lFolder ->
         if(!lFolder.isFile()){
             lFolder.listFiles(dirFilter).each{ innerFindAll(it)}
             files.addAll(lFolder.listFiles(fileNameFilter ))
         }
         return files
     }
     return innerFindAll(folder)
}