Torsten Gonther

Eigene Selektoren für Ant entwickeln

Wer seine Java Builds effizient automatisieren möchte, tut dies in der Regel mit dem Open-Source Werkzeug Ant. Ant zeichnet sich nicht nur durch seine große Funktionsvielfalt, sondern auch durch seine einfache Erweiterbarkeit aus. Bereits in einem früheren iX-Artikel wurde die Entwicklung eigener Ant-Tasks behandelt [1]. In dieser Ausgabe geht es um eine Erweiterung von Ant durch eigene Selektoren. Der vorgestellte Ant-Selektor erlaubt es, Dateien in der Versionsverwaltung CVS anhand ihrer CVS-spezifischen Attribute auszuwählen. Der Artikel gibt zunächst einen Überblick über die Verwendung des zugrunde liegenden Ant-Datentyps FileSet, anschliessend wird das Ant-API zum Erstellen eigener Selektoren erläutert. Um die praktische Anwendung der vorgestellten Konzepte zu illustrieren, wird danach der Code für den CVS-Selektor beschrieben. Für das Verständnis des Artikels sind Java- und Ant-Kentnisse erforderlich.

FileSets: Mengen von Dateien

Um mit einer Menge von Dateien umzugehen, existiert in Ant der Datentyp FileSet. Ein FileSet agiert als virtueller Container, in dem - ausgehend von einem bestimmten Wurzelverzeichnis - eine Teilmenge aller Dateien unter diesem Wurzelverzeichnis enthalten ist. Zum Beispiel beschreibt der XML-Code in Listing 1, Zeile 1 ein FileSet mit dem Wurzelverzeichnis "src", das alle Dateien unterhalb von "src" enthält. FileSets werden in Ant in der Regel als XML-Attribute innerhalb der Definition von Ant-Tasks verwendet. Dadurch wird es möglich, eine eine Menge von Dateien (quasi als Parameter) an einen Task zu übergeben. So besitzt zum Beispiel der Task <javac> ein implizites FileSet-Attribut zur Angabe der Source-Dateien, und beim Task <copy> können mehrere FileSets explizit angegeben werden, um die Menge der zu kopierenden Dateien festzulegen.

Falls nicht alle Dateien unterhalb des Wurzelverzeichnisses in ein FileSet gewählt werden sollen, kann man die Menge der Dateien einschränken, in dem man eine Pattern-Definition angibt. Ein solcher Pattern wird - ähnlich wie Wildcards in einer Shell - auf den Dateinamen angewandt. In Listing 1, Zeile 4 wählt zum Beispiel der Pattern "**/*.class" in der Definition des Attributs <include> alle Java-Class Files unterhalb des Verzeichnisses "src" in das Fileset.

Zusätzlich zur Auswahl von Dateien über ihren Namen gibt es in Ant die Möglichkeit, Dateien mit Hilfe von Selektoren über weitere ganz spezifische Eigenschaften wie die Größe oder das Datum der letzten Änderung auszuwählen. Ein Selektor bezieht sich immer auf eine bestimmte Dateieigenschaft und wird im Ant-Buildfile als zusätzliches Attribut in der XML-Definition des FileSet angegeben. Listing 1 enthält in Zeile 7-9 ein Beispiel, in dessen FileSet alle Dateien enthalten sind, die den String 'image' enthalten. Ant bietet vordefinierte Selektoren für alle allgemein üblichen Dateiatribute an wie <size>, <contains>, <depend>, usw. Diese Selektoren werden in der Online-Hilfe von Ant auch als "Core Selectors" bezeichnet. Darüber hinaus existieren noch Selektor-Container, die es erlauben, Selektoren in logischen Kombinationen anzuwenden (z.B. um alle Dateien zu selektieren, die einen bestimmten String enthalten und die in den letzten zwei Wochen verändert wurden).

=================================================
Listing 1: FileSet Definitionen
1 <fileset dir="src"/>
2
3 <fileset dir="src">
4   <include name = "**/*.class"/>
5 </fileset>
6
7 <fileset dir="src">
8   <contains text="image"/>
9 </fileset>
=================================================

Eine ausführliche Beschreibung des FileSet-Mechanismus inklusive der "Core Selectors" und der Selektor-Container findet sich in der Online-Hilfe zu Ant[2]. Dort sind auch weiterführende Konzepte wie der Datentyp PatternSet und Referenzen auf File- und PatternSets erläutert.

Eigene Selektoren

Für Dateieigenschaften, die nicht über die "Core Selectors" abfragbar sind, kann man in Ant eigene Selektoren programmieren. Ein solcher Selektor besteht aus einer einzelnen Java-Klasse, deren Class-Datei im XML über das Tag <custom> eingebunden wird. Parameter werden einem selbstdefinierten Selektor im XML als Name-Value Paare mit dem <param>-Tag übergeben (siehe Listing 2). Die Funktionalität des Selektors wird innerhalb der Java-Klasse im Wesentlichen durch die Methode "isSelected(..., String filename, ...)" bestimmt. Diese Methode muss einen booleschen Wert zurückgeben, je nachdem ob die als Parameter übergebene Datei in das FileSet gewählt wird oder nicht.
=================================================
Listing 2: FileSet mit Custom Selector
1 <fileset dir="src">
2   <custom classname="MySelectorClass">
3     <param name="aName" value="aValue"/>
4   </custom> 
5 </fileset>
=================================================

Bei der Erstellung eigener Selektoren kann man auf verschiedene Klassen im Ant-API zurückgreifen. Die Struktur dieser API wird im Folgenden erläutert. Wie auch zum Erstellen eigener Tasks (vgl. [1]) existieren in Ant ein Interface und eine abstrakte Klasse zum Erstellen eigener Selektoren. Das Interface und die abstrakte Klasse heißen "FileSelector" respektive "BaseSelector", beide liegen im Package org.apache.tools.ant.types.selectors (siehe Bild 1). Das Interface enthält - analog zur Methode execute() bei den Tasks - nur die Methode boolean isSelected(...). Die abstrakte Klasse implementiert das Interface und ermöglicht ferner den Zugriff auf Ressourcen des Ant-Projekts, in dem sie von der Klasse "DataType" ableitet (Diese Ressourcen können unter anderem verwendet werden, um Log-Ausgaben zu schreiben oder um Properties auszulesen).


Bild 1

Eine Ebene tiefer im Ableitungsbaum existieren das Interface "ExtendFileSelector" und die abstrakte Klasse "BaseExtendSelector". Diese erweitern die oben genannte Kombination aus Klasse und Interface um Mechanismen zur Behandlung von Parametern, wobei die angesprochene Methode isSelected(...) und damit die Klasse "BaseExtendSelector" selbst weiterhin abstrakt bleibt. Die Behandlung von Parametern erfolgt dadurch, dass diese zur Laufzeit vom XML-Parser ausgelesen werden und über die Methode setParameters(...), die im Interface Parameterizable (siehe Bild 1) deklariert ist, dem Selektor übergeben werden. Die Klasse BaseExtendSelector definiert analog zur Methode setParameters(...) eine Methode getParameters(), die man in eigenen Selektorklassen zum Auslesen der Parameter verwenden kann.

Eine Überprüfung, ob alle erforderlichen Parameter korrekt übergeben wurden, kann in der Methode verifySettings() implementiert werden. In der Default-Implementierung der Klasse "BaseSelector" wird diese Methode von der Methode validate() aus aufgerufen. Die Methode validate() dient zur Überprüfung der Konsistenz der Selektor-Instanz und sollte immer als Erstes in der Implementierung der Methode isSelected(...) aufgerufen werden. Eine ausführliche Beschreibung aller erwähnten Klassen und Methoden findet sich in der Online-Hilfe zu Ant [2].

Das Erstellen eigener Selektoren ist mit diesem Grundlagenwissen über FileSets und das Ant-API ist relativ einfach: Ausgehend von Bild 1 definiert man eine Klasse für den neuen Selektor, die von "BaseExtendSelector" ableitet (Falls der neue Selektor keine Parameter zu verarbeiten braucht, kann auch von BaseSelector abgeleitet werden). Anschliessend definiert man in der neuen Klasse für jeden Parameter, der verwendet werden soll, eine Instanzvariable entsprechenden Typs. Die Methode setParameters(...) muss daraufhin neu implementiert werden, so dass diese Instanzvariablen mit Werten belegt werden. Die Funktionalität des Selektors wird wie oben beschrieben in der Methode isSelected(...) implementiert. Die Einbindung in die XML-Datei erfolgt über das <custom>-Tag des FileSets.

Selektor für Versionsverwaltung

Diese Erstellung eines eigenen Selektors wird hier am Beispiel der Versionsverwaltung CVS demonstriert. In einem System zur Versionsverwaltung, einem sogenannten "Version Control System"(VCS), werden in der Regel zu jeder Datei, die sich in dem Versions-Repository befindet, folgende Informationen vorgehalten: Versionen der Datei, Tags bzw. Labels zu den Versionen sowie die Aufteilung der Versionen auf Branches. Einige Systeme zur Versionsverwaltung erlauben auch die Versionierung von Verzeichnissen. Der in diesem Artikel definierte Selektor arbeitet mit den oben erwähnten Attributen Tag, Version und Branch. Dadurch wird es möglich, die Auswahl der Dateien eines FileSet auf einen Branch, eine bestimmte Version oder ein bestimmtes Label zu beschränken. Weiterhin erlaubt der Selektor, zwischen Dateien unter Versionskontrolle und gewöhnlichen Dateien zu unterscheiden. Die Versionierung von Verzeichnissen wird hier nicht berücksichtigt, da CVS dies nicht unterstützt.


Bild2

Da für den neuen Selektor Parameter verwendet werden sollen, muss die neue Selektorklasse von "BaseExtendSelector" abgeleitet werden (s. Bild 2). Um die Implementierung möglichst flexibel zu halten und eine spätere Erweiterung auf andere Systeme zur Versionskontrolle zu ermöglichen, erfolgt diese Ableitung in zwei Schritten gemäß dem Strategie-Entwurfsmuster. Es wird zunächst eine Klasse "AbstractVCSSelector" abgeleitet. In dieser Klasse wird die Übergabe der VCS-Parameter (Branch, Tag, usw.) implementiert. Die Klasse implementiert außerdem bereits die Funktionalität des Selektors in der Methode isSelected(...), verwendet hierfür aber eigens definierte abstrakte Methoden, die in den konkreten Subklassen von AbstractVCSSelector ausprogrammiert werden müssen (z.B. die Methode checkTag()). Die Übergabe der Parameter findet wie beschrieben in der Methode setParameters(...) statt. Es können folgende Parameternamen verwendet werden: Branch, Label und Version. Wird kein Parameter übergeben, prüft der Selektor nur, ob es sich um eine Datei unter Versionkontrolle handelt. Falls ein Parameter oder eine Kombination der genannten Parameter übergeben wird, geht der Selektor von einer VCS-Datei aus und prüft, ob die Bedingungen aus den Parametern auf die Datei zutreffen. Für Verzeichnisse prüft dieser abstrakte Selektor keine Versionsinformation, sondern gibt immer true zurück. Diese Voreinstellung kann für eine konkrete Subklasse des Selektors geändert werden, indem dort die Instanzvariable "checkDirs" mit dem Wert true belegt wird.
hier Listing 3: Datei AbstractVCSSelector.java

Von der Klasse "AbstractVCSSelector" leitet dann der konkrete Selektor fürs CVS ab ("CVSSelector"). Die Funktionalität wird dort wie oben beschrieben durch Implementierung der geerbten abstrakten Methoden bereitgestellt. Dazu werden noch zwei Hilfsmethoden definiert, die über das Kommandozeileninterface von CVS den CVS-Status einer Datei abfragen können.
hier Listing 4: Datei CVSSelector.java

Als Anwendungsbeispiel ist in Listing 3 die XML-Definition eines FileSets angegeben, das alle Java-Dateien unterhalb des Wurzelverzeichnisses "src" enthält, sofern es sich dabei um CVS-Dateien handelt. Weitere Java-Dateien, zum Beispiel solche, die mittels idl2java oder castor automatisch generiert werden, werden von dem FileSet nicht erfasst.

=================================================
Listing 5: Anwendungsbeispiel
1 <fileset dir="src">
2   <custom classname="VCSSelectors.CVSSelector">
4   </custom> 
5 </fileset>
=================================================

Eine Erweiterung der vorgestellten Selektoren auf andere Systeme zur Versionsverwaltung ist durch die Verwendung des Strategie-Musters einfach möglich. So wäre zum Beispiel für ClearCase lediglich folgendes tun: Analog zur Klasse CVSSelector müßte eine Klasse "CCSelector" entwickelt werden, in deren Implementierung anstatt des Befehls "cvs status ..." der Befehl "cleartool ls ..." verwendet und das Ergebnis entsprechend ausgewertet wird. Da ClearCase mit versionierten Verzeichnissen arbeitet, muss außerdem die Instanzvariable "checkIsDir" im Konstruktor mit dem Wert "true" initialisiert werden. Weiterhin ist auch eine Erweiterung des CVS-Selektors selbst denkbar, z.B. zur Unterscheidung zwischen Text- und Binärdateien. Dies kann auf einfache Weise durch Vererbung geschehen: Die Implementierung der Methode setParameters(...) muss einen weiteren Parameter verarbeiten können: CVSType. Dieser Parameter kann die Werte "text" oder "binary" haben. Die Methode setParameters(...) muss in der Klasse "CVSSelector" überschrieben werden und prüfen, ob der Parameter CVSType gesetzt wurde. Die übrigen Parameter werden wie gewohnt durch Aufruf von super() mit der Default-Implementierung verarbeitet. Analog dazu wird die Methode isSelected(...) ebenfalls überschrieben, um bei vorhandenem Parameter CVSType die erforderliche Überprüfung durchzuführen und den Rest an die Superklasse zu delegieren.

Fazit

Der Artikel hat gezeigt, wie die offene Architektur von Ant ausgenutzt werden kann, um auf einfache Weise die Funktionalität von Ant zu erweitern. Durch die mehrstufige Architektur erhält die Erweiterung selbst genügend Flexibilität, um auch auf zusätzliche Bedürfnisse und andere Zielsysteme angepasst werden zu können.

Referenzen [1] Fühlersprache, iX 02/2004 [2] Online-Manual zu Ant