October 2007

Subversion mit Ant automatisieren

So gut wie jedes kleinere oder größere Projekt nutzt irgendeine Form der Versionskontrolle, wie beispielsweise Subversion oder CVS. Dabei ist allerdings darauf zu achten, dass die Änderungen, die man am Code vornimmt, auch in sehr regelmäßigen Abständen mit dem Repository synchronisiert werden. Man kann entweder von Hand immer wieder den Commit und Checkout starten, man kann sich durch GUI-Werkzeuge dabei helfen lassen, wie beispielsweise mit Subclipse für die Eclipse IDE oder mit TortoiseCVS oder aber man automatisiert diesen Vorgang einfach, indem man innerhalb eines Ant-Buildfiles diesen Vorgang startet.

Dazu muss man sich SvnAnt herunterladen (das sind nur 182 KB). Und dann kopiert man die Dateien svnant.jar, svnClientAdapter.jar und svnjavahl.jar in den (typischen) /lib -Ordner des Projekts. Wenn das Projekt bereits mit Subversion verwaltet wird, dann sind alle nötigen Informationen für svnAnt bereits vorhanden und die Benutzung kann in wenigen Zeilen in dem Ant-Buildfile geschehen. Die Task-Def ist durch ein bereits in die svnant.jar integrietes Property-File besonders einfach.

In diesem Beispiel kann zwar immer 'ausgecheckt' werden, allerdings findet das 'Einchecken' (commit) nur bei erfolgreichen Test-Durchlauf statt. Das ist vielleicht ganz sinnvoll, damit kein kaputter Code ins Repository gelangt. Wenn man sehr gerne Releases publiziert, dann kann man natürlich auch wunderbar eine binäre Distribution seines Projekts mit zugehöriger Versions- oder Revisions-Nummer einchecken. Auf diese Weise kann dann auf der möglicherweise auf der Website des Projekts immer ein Nightly-Build angegeben werden, ohne dass dafür besondere Software (wie beispielsweise CruiseControl) auf dem Server installiert sein muss.

<taskdef resource="svntask.properties" classpathref="ProjectLib.classpath"/>
 
<target name="checkout">
    <svn>
        <update dir="src" />
    </svn>
</target>
 
<target name="checkin" depends="checkout, test">
    <svn>
        <commit message="Automatic Checkin" dir="src" />
    </svn>
</target>

(originally posted on 2007-10-31)

Exceptions werfen und fangen

Ein weiterer schwerwiegender Unterschied zwischen Java und C# ist die Behandlung von Exceptions. In Java muss jede Exception, die geworfen wird, bereits in der Methodendefinition enthalten sein und außerdem müssen alle Exceptions(Ausnahmen) auch wirklich in der aufrufenen Methode behandelt werde, also entweder weitergeworfen oder gefangen werden.

In verschiedenen Fällen führt sowohl in C# alsauch in Java ein naives Programmieren zu Code, der eher wenig mit Exceptions arbeitet - vor allem, wenn der Programmierer zu optimistisch ist und immer nur den korrekten Programm-Ablauf vor Augen hat. Dabei gibt es viele Szenarien, in denen Ausnahmen auch ganz bewusst geworfen und behandelt werden um auf Fehler möglichst schnell und auch eindeutig reagieren zu können. Dass entspricht in etwa der berühmten "Regel des Reparierens: Wenn das Programm scheitert, soll es das lautstark und so früh wie möglich tun.", die ein Teil der so genannten UNIX-Philosophie ist.

Ein kleines Beispiel in Java nutzt Exceptions aus, allerdings sollte man den Code nicht einfach kopieren, da Exceptions wesentlich mehr sinn machen, wenn man entsprechende Ableitungen für verschiedene Situationen bildet und nicht immer die unspezifische Exception nutzt.

private String dataToString(Collection<String> data) throws Exception {
    StringBuilder sb = new StringBuilder();
    for (String s : data) {
        if (s == null) {
            throw new Exception("Data should not be null");
        }
        sb.append(s);
        sb.append('\n');
    }
    return sb.toString();
}
 
private void saveToFile(File file) throws Exception {
    if (file.exists() && !file.canWrite()) {
        throw new Exception("File exists already and could not be overwritten.");
    }
    Writer wr = new FileWriter(file);
    BufferedWriter br = new BufferedWriter(wr);
    try {
        String s = dataToString();
        br.write(s);
    } finally {
        br.close();               
    }
}
 
private File chooseFile() throws Exception {
    JFileChooser chooser = new JFileChooser();
    int result = chooser.showSaveDialog(this);
    switch (result) {
        case JFileChooser.ERROR_OPTION:
            throw new Exception("An Error ocurred");
        case JFileChooser.CANCEL_OPTION:
            throw new Exception("Saving Cancelled by User");
        default:
            return chooser.getSelectedFile();
    }
}        
 
/* . . . . */
saveButton = new JButton();
saveButton.setText("Save");
saveButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        try {
            File file = chooseFile();
            saveToFile(file);
        } catch(Exception e2) {
            errorLabel.setText(e2.getMessage());
            errorLabel.setVisible(true);
        }
    }
});

(originally posted on 2007-10-19)

Erweiterbare Anwendungen mit Addins

Im Moment gibt es den Trend, Anwendungen mithilfe von Plug-ins bzw. Add-ons bzw. Add-ins erweiterbar zu machen. Dabei gibt es natürlich viele verschiedene Ansätze und Frameworks. Wer im .NET Bereich allerdings keine Software einkaufen möchte, der wird auf das freie Mono.Addins treffen und auch auf das im .NET Framework 3.5 enthaltene System.Addin zurückgreifen.

Aus der Sicht des Hosts - also des festen Kerns der Anwendung - sehen die beiden Frameworks relativ ähnlich aus, allerdings funktioniert die Addin-Verwendung intern komplett anders.

System.AddinMono.Addins
VorteileEs ist möglich Addins wieder zu entladen und somit auch zur Laufzeit zu ersetzen, ohne das der Host es überhaupt merkt. Durch Entkoppelung können Änderungen am Host oder auch an den Addins durchgeführt werden, ohne dass die Addins oder Hosts neugebaut werden müssen.Extrem einfache Architektur: Eine gemeinsam definierte Schnittstelle wird direkt vom Addin implementiert und vom Host genutzt. Die Addins liegen in derselben AppDomain und können somit wie ganz normale Objekte verwendet werden.
NachteileEs wird eine 'Pipeline' benötigt, sodass für jeden gemeinsam genützten Typ eine Version auf dem Host, eine Version für die Addins, ein gemeinsamer 'Contract', sowie vier Adapter-Klassen geschrieben werden müssen.Es ist nicht möglich ein geladenes Addin wieder zu entladen. Somit muss bei jeder Aktualisierung eines Addins die Anwendung neugestartet werden.
BeispieleExtensibleCalculatorWriterService

(originally posted on 2007-10-09)

Manchmal muss es der Doppelklick sein

Im Server-Bereich, in der Linux-Welt und auch sonst überall wo Techniker arbeiten, stellt das Ausführen von Java-Anwendungen oder Anwendungen, die in Scriptsprachen geschrieben sind, kein Problem dar. Allerdings ist der Durchschnitts-Windows-Benutzer manchmal doch von .class oder auch .jar-Dateien verwirrt oder weiß noch nicht einmal ob er denn Java installiert - geschweige denn wo. Auch wenn man für solche Fälle Batch-Scripte, wie beispielsweise in Form von .bat oder .sh-Dateien mitliefert, macht das dann keinen guten Eindruck.

Die Lösung: Die Java-Desktop-Anwendung einfach als EXE-Datei ausliefern. Dazu kann man entweder JSmooth benutzen oder aber launch4j (beides sind Sourceforge-Projekte unter sind unter der LGPL verfügbar).

JSmooth ist eher für denjenigen gedacht, der ein fertiges Projekt für Windows-Nutzer ausliefern möchte und bei der Konfiguration von einer GUI begleitet werden möchte. launch4j hingegen ist ein reines Konsolen-Werkzeug, dass aber durch die einfache Verwendung in einem Ant-Script eigentlich bei jeder kleinen bis mittelgroßen Java-Anwendung angebracht ist.

Wenn man sowieso bereits ein Ant-Buildfile hat, dann reichen folgende neun Zeilen aus, um eine vollkommen nativ-wirkende EXE-Datei bei jeder Kompilierung automatisch mitzuerstellen:

<taskdef name="launch4j" classname="net.sf.launch4j.ant.Launch4jTask" />
 
<launch4j>
    <config headerType="0" jar="MyApp.jar" outfile="MyApp.exe">
        <jre minVersion="1.5.0" />
    </config>
</launch4j>

(originally posted on 2007-10-03)