Clojure Erfinder Rich Hickey über Zeit und die Nachteile von OOP

Clojure und Funktionale Programmierung ist für mich derzeit ein sehr interessantes Thema. Von C++ und Java kommend, mit Objekt orientierter Programmierung als state of the art Paradigma, ist die Tatsache, dass Clojure nicht OO ist und es auch gar nicht versucht, zunächst durchaus abstoßend. Ich bin auch noch nicht überzeugt davon, dass komplett davon ab zu kommen richtig ist. Rich Hickey, der Erfinder von Clojure, ist auch der Meinung, dass das Denken in Identitäten für uns Menschen sehr natürlich und verständlich ist und das man dies nicht komplett verwerfen sollte. Dennoch erklärt er, was an unserer derzeitigen Sichtweise falsch ist.

„You cannot step twice into the same river.“ (Heraclitus)

(Der Fluss ist nur ein Konstrukt das wir errichten, er besteht in Wirklichkeit nur aus Wassermolekülen, die zu einem späteren Zeitpunkt allerdings ganz andere sind. Einmal ganz davon abgesehen, dass allein die Tatsache dass die Teilchen sich „geändert“ haben, so etwas wie Zeit erzeugt wird. Mehr dazu aber im Vortrag.)

Ich selber kann den Inhalt, des über eine Stunde dauernden Vortrags, in dieser Kürze sicher nicht wiedergeben und ihr solltet euch lieber selber anhören. Er macht einem einige Dinge bewusst, die einem das Leben im Programmieralltag durchaus erleichtern können. Es handelt sich hierbei nicht direkt um einen Vortrag über Clojure, sondern vielmehr um Grundlagen über die damit Verbunde Herangehensweise. Zugegebenermaßen klingt diese zu Anfang fast schon Philosophisch, was wahrscheinlich nicht von Ungefähr kommt, wie man im Laufe des Videos erfährt. Gegen Ende kommt der Vortrag dann zu Konstrukten und Implementierungsmöglichkeiten, wie sie in Clojure verwendet werden und schließt damit den Kreis zu möglichen Anwendungen. Von sich selber sagt Hickey nämlich er sei ein Pragmatiker; einer der sich sehr gut der Theoretischen Grundlagen bedient, wie ich finde.

http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey

Vielleicht kann man in Zukunft einen Weg finden OO und Funktionale Programmierung zu vereinen. Ich werde mich erst einmal weiter mit Clojure beschäftigen und kann dann vielleicht entscheiden man hier letztendlich bei Spaghetticode landen wird.

Advertisements

GWT – Google-Web-Toolkit, Einführung und Meinung dazu

Ich möchte hier kurz auf einen Artikel eines Kollegen über GWT hinweisen. Dieser befasst sich sehr oberflächlich mit GWT, gibt aber vielleicht eine Motivation sich, ein wenig näher damit zu befassen.

http://blog.seibert-media.net/2010/12/08/gwt-evolution-der-internet-anwendung/

Ich finde es auch sehr angenehm mit GWT zu arbeiten.

Man kommt kaum noch in Berührung mit den syntaktischen Unzulänglichkeiten von Javaskript und kann auf angenehme Weise mächtige und performante “RIA” Anwendungen entwickeln.

Ein Punkt den ich persönlich jedoch wichtig finde ist, dass sich GWT am besten eignet, wenn man tatsächlich Anwendungen entwickelt. Das heißt, anstatt zu versuchen klassische voll designte und dynamische Webseiten damit zu bauen.
Die mitgebrachten Widgets und Layouts kommen nun einmal aus der AWT/Swing Ecke und gliedern sich auch am natürlichsten in solch eine Art Anwendung ein.
Eine voll designte Anwendung, in der man auf das look and feel von Standardelementen zwecks Individualismus verzichtet, ist zwar möglich, bedeutet hier aber etwas mehr Aufwand als bei klassisch Server-Seitig erzeugten HTML Seiten.

Dass solche eine GWT Anwendung dennoch gut aussehen kann, beweist meiner Meinung nach der PostgreSQL GWT Client aus dem GWT Showcase http://demo.teampostgresql.com/

Sehr schön sieht natürlich auch https://www.twentyfeet.com/app/ aus, auch wenn es hier viele klassische Webseiten Elemente gibt, deren Umsetzung aufwändiger ist als bei Server generierten Seiten.

Rome und Felix OSGi

Ich war freudig überrascht, als ich Rome (RSS/Atom syndication and publishing tools) gefunden hatte. Es macht einen soliden, gut designten und dokumentierten Eindruck. Dies und die Tatsache, dass ich ansonsten nichts vergleichbares gefunden habe, ist auch der Grund warum ich so viel Aufwand betrieben habe die Bibliothek zum Laufen zu bekommen.

In unserem Projekt verwenden wir Felix OSGi, was nicht selten zu Problemen bei der Integration von externen Bibliotheken geführt hat.

Im Fall von Rome äußterte sich das in folgenden Exceptions:

SEVERE: Servlet.service() for servlet proxy threw exception
java.lang.NoClassDefFoundError: Could not initialize class com.sun.syndication.feed.synd.SyndFeedImpl
[...]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
[...]

bzw.

SEVERE: Servlet.service() for servlet proxy threw exception
java.lang.NullPointerException
at java.util.Properties$LineReader.readLine(Properties.java:418)
at java.util.Properties.load0(Properties.java:337)
at java.util.Properties.load(Properties.java:325)
at com.sun.syndication.io.impl.PropertiesLoader.(PropertiesLoader.java:74)
at com.sun.syndication.io.impl.PropertiesLoader.getPropertiesLoader(PropertiesLoader.java:46)
at com.sun.syndication.io.impl.PluginManager.(PluginManager.java:54)
at com.sun.syndication.io.impl.PluginManager.(PluginManager.java:46)
at com.sun.syndication.feed.synd.impl.Converters.(Converters.java:40)
at com.sun.syndication.feed.synd.SyndFeedImpl.(SyndFeedImpl.java:56)
[...]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)

Letztere Exception tritt auf, weil ein Property File rome.properties nicht gefunden werden kann und dies nicht weiter abgefangen ist.

Versuch 1: Mit diesem Problem brachte mich google zu einigen Leuten die auch das Problem hatten. Es scheint sogar einen Patch zu geben und eine Rome Version (1.0-osgi) im mvn Repository des Autors. Leider hat dies bei mir nicht zu einer Lösung geführt. Quelle und Rome Bugtracking Ticket dazu.

Versuch 2: Interessanterweise stand auf der Rome Seite auch etwas zum Thema Rome + OSGi, worauf mich google nicht geführt hat. Hier ist von einer rome.pluginmanager.useloadclass Property bei RC2 die Rede. Das Setzen der Property auf true hat allerdings auch nicht den gewünschten Effekt gehabt. Der Code der dadurch ausgeführt werden soll sieht auch nicht so aus als würde er helfen. Wer es dennoch versuchen möchte: Rome Homepage : RomeAndOSGi

Lösung: Was letztendlich funktioniert hat, war den Rat von JS Bournival zu befolgen und die Bibliothek selber zu patchen. Dabei muss nicht der Thread ClassLoader verwendet werden, sondern der selbe, der auch verwendet wurde um die Rome Klassen zu laden. Dieser hat dann auch Zugriff auf die Property Files die Rome in der Exception vermisst hat. (Seltsamerweise macht die 1.0-osgi dies angeblich auch so) OSGi: jumping through classoading hoops

Step by Step + Patch:

Ich habe folgendes gemacht:

Rome 1.0 RC2 checkout:

cvs -d :pserver:username@cvs.dev.java.net:/cvs checkout -r v1_0RC2-CORE rome

(Ihr braucht ein java dev Konto, oder falls ihr eures einmal verlegt haben solltet gibt es eine coole Seite bugmenot welche auch für Burger King Gutscheine sehr hilfreich ist.)

grep -sIR "Thread.currentThread().getContextClassLoader()" .

Eine einfache Suche nach der Verwendung des falschen ClassLoaders zeigt die zu Ändernden Klassen auf. Schön zu sehen dass hier auch mit dem bewährten Copy-Paste-Pattern gearbeitet wurde ;-).

Ihr könnte euch das sparen und einfach den Patch anwenden http://gist.github.com/633790

$ cd rome
$ patch -p0 < rome-v1_0RC2-osgi.patch

Das builden mittels mvn install sowie die Tests sollten durchlaufen und das jar kann manuell in euer maven repository installiert werden.

hth

Übersicht über Java Collections

Ein Kollege hat heute einen Link zu einer schönen Übersicht über die Standard Java Collections herumgeschickt.

http://www.torsten-horn.de/techdocs/java-collections.htm

Perl Skript zum Aktualisieren eines OSGi Bundles in Apache Felix

Wenn ihr Apache Felix nutzt und die telnet Shell aktiviert habt

osgi.shell.telnet.port=6666

Könnt ihr mit folgendem Skript einfach eure Bundles aktualisieren.

#!/usr/bin/perl

use strict;
use warnings;

use Net::Telnet;

if (@ARGV != 2) {
        print "$0: [bundle name substring] [bundle jar absolute path]\n";
        exit 1;
}

my $bname = shift;
my $bfile = shift;

my $host = 'localhost';
my $port = 6666;

sub get_bundle_id($$) {
        my ($procs, $bname) = @_;
        my @proc = grep { m/$bname/i } @$procs;
        die "No bundle $bname found." unless @proc > 0;
        warn "Multiple bundles $bname found." if @proc > 1;
        $proc[0] =~ m/^\[\s*(\d+)\s*\]/;
        return $1;
}

my $prompt = '/->/';
my $telnet = new Net::Telnet ( Timeout=>10, Errmode=>'die', Prompt => $prompt);
$telnet->open(Host => $host, Port => $port);
$telnet->waitfor($prompt);

my @procs = $telnet->cmd('ps');

my $bid = get_bundle_id(\@procs, $bname);
print "Bundle id for $bname is $bid\n";
print "Updating bundle $bid with $bfile\n";

my @result = $telnet->cmd("update $bid file:$bfile");
print "Error: ", $result[0], "\n" if (@result > 0 && $result[0] =~ m/unable|error|fail/i);
print "done.\n";

Dies kann auch einfach in Maven integriert werden, dazu muss einfach sicher gestellt werden, dass das Skript im PATH vorhanden oder der Absolute Pfad angeben wird:

<!-- Directly deploy to osgi -->
<plugin>  
     <artifactId>exec-maven-plugin</artifactId>  
     <groupId>org.codehaus.mojo</groupId>  
     <version>1.1</version>
     <executions>
	     <execution>
	       <phase>install</phase>
	       <goals>
	         <goal>exec</goal>
	       </goals>
	     </execution>
     </executions>
     <configuration>  
       <executable>osgi_deploy.pl</executable>  
       <arguments>  
           <argument>${project.name}</argument>  
           <argument>${project.build.directory}/${project.build.finalName}.jar</argument>  
       </arguments>  
    </configuration>  
</plugin>

Die Ausführung:

$ mvn exec:exec
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building MyProject
[INFO]    task-segment: [exec:exec]
[INFO] ------------------------------------------------------------------------
[INFO] [exec:exec {execution: default-cli}]
[INFO] Bundle id for MyProject is 5
[INFO] Updating bundle 5 with /home/bpeter/workspaces/MyProject/trunk/myproject/target/myproject.jar
[INFO] done.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

hf

Apache Maven Beispiel

Maven ist ein build Tool und Framework für java, welches es sich zum Ziel gemacht hat dem Anwender möglichst viele Schritte abzunehmen. In der allmächtigen pom.xml werden alle relevanten Optionen konfiguriert und ansonsten mit Konventionen in Form von Verzeichnissturktur und Dateinamen gearbeitet.

Das Framework erhält durch seine Pluginfähigkeit eine gute Flexiblität und ermöglicht viele Aufgaben durch ein wenig Konfiguration zu lösen.

Ich will hier eine maven Version meines vorherigen Apache ant Beispiels zeigen, die zum gleichen Ergebnis führt. Zudem gibt es einen kleinen Unit-Test, da das Framework deren Einbindung direkt mitbringt.

Am Anfang ist hier der getting started Guide von maven ein guter Anhaltspunkt http://maven.apache.org/guides/getting-started/index.html.

Mit folgendem Befehl legt man ein neues, leeres Projekt an. Weitere Infos werden anschließend in der pom.xml konfiguriert, dessen finale Version ich weiter unten aufgelistet und mit Kommentaren versehen habe.

mvn archetype:create \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DgroupId=de.benjaminpeter \
-DartifactId=print

Code

Verzeichnisstruktur

Die Grundstruktur von Maven wurde mit dem create Befehl angelegt und ist dabei allgemein wie folgt:

pom.xml
src/main
src/resources
src/test
src/test/resources

Im konkreten Beispiel mit allen Klassen und Ressourcen:

./pom.xml

./src
./src/main
./src/main/java
./src/main/java/de
./src/main/java/de/benjaminpeter
./src/main/java/de/benjaminpeter/print
./src/main/java/de/benjaminpeter/print/LoadFile.java
./src/main/java/de/benjaminpeter/print/Print.java

./src/main/resources
./src/main/resources/resources
./src/main/resources/resources/inputA
./src/main/resources/resources/inputB
./src/main/resources/resources/merge
./src/main/resources/resources/file

./src/test
./src/test/java
./src/test/java/de
./src/test/java/de/benjaminpeter
./src/test/java/de/benjaminpeter/print
./src/test/java/de/benjaminpeter/print/LoadFileTest.java

./src/test/resources
./src/test/resources/resources
./src/test/resources/resources/file

Java src (analog zum ant Beispiel)

import java.io.*;
import java.lang.*;

public class LoadFile {
  public String load() throws FileNotFoundException, UnsupportedEncodingException, IOException {
    // File file = new File("resources/file");
    // FileInputStream fis = new FileInputStream(file);
    // Datei ist nun im jar, also muss sie anders geladen werden
    ClassLoader cl  = this.getClass().getClassLoader();
    InputStream fis = cl.getResourceAsStream("resources/file");
    // import groovy ... file.text ... :-/
    BufferedReader in = new BufferedReader(new InputStreamReader(fis));
    String str = "";
    String ret = "";
    while ((str = in.readLine()) != null) ret += str + '\n';
    return ret.substring(0, ret.length() - 1);
  }
}

import java.lang.*;

public class Print {
  public static void main(String[] args) {
    System.out.println("Hello.");
    try {
      System.out.println("Content:\n"+ 
        new LoadFile().load());
    }
    catch (Exception e) {
      System.err.println("Error: "+ e.getMessage());
    }
  }
}

Ressourcen

File: resources/file

A: Build @buildNumber@
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the
B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile. 
File: resources/inputA

A: Build @buildNumber@
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the
File: resources/inputB

B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile. 
File: resources/merge

#!/bin/sh
cat $1 $2 > $3

Hierbei musste ich @date@ zu @buildNumber@ ändern, da es ohne größeren Aufwand nicht möglich properties in anderen properties zu verwenden. (Falls es jemand besser weiß immer her damit, finde das durchaus wichtig)

pom.xml

<project
   xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <!-- Basisinformationen zum Projekt -->
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.benjaminpeter.print</groupId>
  <artifactId>Print</artifactId>
  <packaging>jar</packaging>
  <!-- Version des Projekts in maven, der Name des
       Jars kann noch einmal extra definiert werden! -->
  <version>0.0.1</version>
  <name>Print</name>
  <url>http://maven.apache.org</url>
  <!-- Default dependency fuer Unit-Tests, bibliothek
     wird automatisch herunter geladen -->
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <!-- scope: 
           Gibt an, dass diese Abhaengigkeit nur
           zum Testen benötigt wird. Das heißt sie wird
           auch nur beim Ausführen der Tests
           heruntergeladen und ist im
           finalen jar/war nicht enthalten. -->
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <!-- Binde die resource "resources/file" ein, dies
         wird dynamisch erzeugt wie wir später sehen. -->
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>resources/file</include>
        </includes>
         <!-- Zudem soll hier das
          Filtering aktiviert werden,
         das heißt wir lassen maven einen
         Platzhalter @buildNumber@ in der
         Datei ersetzen. -->
        <filtering>true</filtering>
      </resource>
    </resources>
    <plugins>
      <!--
        Binde ein Plugin ein um die Build Nummer
        variabler erstellen zu können. Hiermit machen
        wir die Variable buildNumber vom aktuellen
        Datum abhängig.
        Auch das Plugin wird von maven bei Bedarf
        dynamisch herunter geladen. -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>buildnumber-maven-plugin</artifactId>
        <configuration>
          <format>{0,date,yyyyMMdd}</format>
          <items>
            <item>timestamp</item>
          </items>
        </configuration>
        <!-- Diese Block besagt, dass das Plugin in
             der validierungsphase (ganz am Anfang)
             ausgeführt werden soll. create ist dabei
             eine von mehreren Funktionen des Plugins
             die dabei aufgerufen werden soll. -->
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>create</goal>
            </goals>
          </execution>
        </executions>
       </plugin> 
       <!-- Mit dem jar Plugin können wir
          die Erzeugung des jar Files etwas
          anpassen. So setzen wir hier die
          MainClass fest um ein einfach
         ausführbares jar zu bekommen. -->
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
         <configuration>
           <archive>
            <manifest>
              <mainClass>Print</mainClass>
              <packageName></packageName>
            </manifest>
            <manifestEntries>
              <mode>development</mode>
              <url>${pom.url}</url>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
      <!-- exec Plugin, dies ermöglicht es alle
           Schweinereien zu machen die man
           sonst nicht hinbekommt.
           Wird hier auch zur Verfizierungsphase
           ausgeführt und ruft den Befehl "merge"
           aus dem resources Verzeichnis auf
          um die Datei "file" zu erzeugen.
           
           An dieser Stelle könnte man sicher auch
           ein Ant Plugin einbinden um komplexere
           Abläufe zu realisieren und Abhängigkeiten
           sowie bereits angelegte Dateien erkennen
           zu können. -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
            <phase>verify</phase>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <executable>./merge</executable>
          <workingDirectory>${basedir}/src/main/resources/resources</workingDirectory>
          <arguments>
            <argument>inputA</argument>
            <argument>inputB</argument>
            <argument>file</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
    <!-- Setze den Namen der jar Datei so, dass
         auch unsere im Plugin
         erzeugte buildNumber eingesetzt wird. -->
    <finalName>${project.artifactId}-${buildNumber}</finalName>
  </build>
  <properties>
    <!-- Funktioniert leider nicht :-/ -->
    <date>${buildNumber}</date>
  </properties>
</project>

Ausführung

Build

$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Print
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
Downloading: http://repo1.maven.org/maven2/net/java/dev/jna/jna/3.0.5/jna-3.0.5.pom
Downloading: http://repo1.maven.org/maven2/net/java/dev/jna/jna/3.0.5/jna-3.0.5.pom
[INFO] [buildnumber:create {execution: default}]
[INFO] Storing buildNumber: 20091030 at timestamp: 1256918122611
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 2 source files to /home/dedeibel/proggn/java/test/mvn-example/Print/target/classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Compiling 1 source file to /home/dedeibel/proggn/java/test/mvn-example/Print/target/test-classes
[INFO] [surefire:test]
[INFO] Surefire report directory: /home/dedeibel/proggn/java/test/mvn-example/Print/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running LoadFileTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.031 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jar:jar]
[INFO] Building jar: /home/dedeibel/proggn/java/test/mvn-example/Print/target/Print-20091030.jar
[INFO] [exec:exec {execution: default}]
[INFO] [install:install]
[INFO] Installing /home/dedeibel/proggn/java/test/mvn-example/Print/target/Print-20091030.jar to /home/dedeibel/.m2/repository/de/benjaminpeter/print/Print/0.0.1/Print-0.0.1.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7 seconds
[INFO] Finished at: Fri Oct 30 16:55:26 CET 2009
[INFO] Final Memory: 31M/290M
[INFO] ------------------------------------------------------------------------

Programm

$ java -jar target/Print-20091030.jar         
Hello.
Content:
A: Build 20091030
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the
B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile.

Fazit

Den direkten Vergleich zwischen ant und maven fand ich sehr interessant. Im kleinen Beispiel kann man die Stärken von maven jedoch nur erahnen. So hätte es bei ant wieder einer extra Einbindung der Unit-Tests bedurft, die aber im Grunde für jedes Projekt gleich ist wenn man sich an die Konventionen hält. Weiterhin musste kein Finger krum gemacht werden um eine aktuelle Version der junit Abhängigkeit zu bekommen; ein großer Vorteil bei einem Umzug auf ein anderes System, sofort werden die benötigten Abhängigkeiten ohne Aufwand nachgezogen.

Der Teufel steckt im Detail, während das Anpassen des Dateinamens und Ausführen des merge Skripts bei ant ein Kinderspiel sind, da man hier direkt auf die Abhängigkeiten und jar Erstellung Einfluss hat, scheint der Weg bei Maven für alles ein Plugin installieren zu müssen doch etwas umständlich. Letztendlich ist es aber eine Gute Sache, da es Plugins für die Unterschiedlichsten Aufgaben gibt und diese einem wie maven selbst möglichst viel Arbeit abnehmen. Allgemein sollte man sich vielleicht weniger mit Details aufhalten und gerade bei Verwendung von Frameworks Kompromisse eingehen und sich an die Gegebenheiten gewöhnen. Bei großen Projekten ist es auch so schwer genug die Übersicht zu behalten.

Quellen

Apache maven
Maven getting started

Die Vernachlässigung von tollen Namespaces für meine Klassen sie mir verziehen.

Apache Ant Beispiel

Apache ant ist ein build Tool, hauptsächlich für Java, was XML Dateien als Konfiguration verwendet.

Ant war früher sehr populär und ist nun z.B. durch maven erweitert oder ivy ersetzt, da diese über automatische Abhängigkeitsauflösung verfügen. Beim Umgang mit maven können einige Ant Grundlagen nicht schaden.

Das folgende Beispiel baut zwei Klassen und packt diese dann mit den Resource files in ein gemeinsames Jar. Die Resource Dateien werden zuvor noch mit einem Zeitstempel versehen und zusammengeführt um die Möglichkeiten von ant zu demonstrieren.

In einem zweiten Beitrag zeiche ich die Umsetzung des gleichen Projektes mit maven. Apache Maven Beispiel

Code

Dateien

./build.xml
./resources
./resources/inputA
./resources/inputB
./resources/merge
./src
./src/LoadFile.java
./src/Print.java

Java Source

import java.io.*;
public class LoadFile {
  public String load() throws FileNotFoundException, UnsupportedEncodingException, IOException {
    char[] buff = new char[10240];
    // File file = new File("resources/file");
    // FileInputStream fis = new FileInputStream(file);
    // Datei ist nun im jar, also muss sie anders geladen werden
    ClassLoader cl  = this.getClass().getClassLoader();
    InputStream fis = cl.getResourceAsStream("resources/file");
    InputStreamReader in = new InputStreamReader(fis, "UTF-8"); 
    in.read(buff, 0, 10240);
    return new String(buff);
  }
}

import java.lang.*;

public class Print {
  public static void main(String[] args) {
    System.out.println("Hello.");
    try {
      System.out.println("Content:\n"+ new LoadFile().load());
    }
    catch (Exception e) {
      System.err.println("Error: "+ e.getMessage());
    }
  }
}

Ressourcen

File: resources/inputA
A: Build @date@
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the

File: resources/inputB
B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile. 

File: resources/merge
#!/bin/sh
cat $1 $2 &gt; $3

Ant build.xml

<project name="Print" default="build" basedir=".">
    <description>
        Print example build file
    </description>
  <!-- set global properties for this build -->
  <property name="src" location="src"/>
  <property name="build" location="build"/>
  <property name="dist"  location="."/>
  <property name="resources" location="resources"/>

  <!-- Vorbereitung der Resourcen -->
  <target name="prep" depends="init">
    <!-- Aufruf des Merge Befehls mit den Dateipfaden als Argumenten -->
    <exec executable="${resources}/merge">
      <arg value="${resources}/inputA"/>
      <arg value="${resources}/inputB"/>
      <arg value="${build}/resources/file.in"/>
    </exec>
    <!-- Setze einen Filter, dieser kann das Vorkommen von @token@
         in Dateien während Kopierbefehlen durch einen Wert ersetzen.
         Dazu muss bei copy zum Beispiel filterting="true" angegeben werden.
         
         Hier wird ein Vorkommen von @date@ durch einen Timestamp ersetzt.
         Die property DSTAMP kommt dabei vom Befehl mkstamp, welcher im
         target init ausgefuehrt wurde.  -->
    <filter token="date" value="${DSTAMP}"/>
    <!-- Kopiere die Datei um und wende dabei die Filter an -->
    <copy tofile="${build}/resources/file" file="${build}/resources/file.in" filtering="true"/>
    <delete file="${build}/resources/file.in"/>
  </target>

  <target name="init">
    <!-- Create the time stamp, verfügbar als ${DSTAMP} -->
    <tstamp/>
    <property name="outjar" value="${dist}/${ant.project.name}-${DSTAMP}.jar"/>
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}"/>
    <mkdir dir="${build}/resources"/>
  </target>

  <target name="compile" depends="init"
      description="compile the source " >
    <!-- Compile the java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}"/>
  </target>

  <target name="build" depends="compile, prep"
      description="generate the distribution" >
    <jar jarfile="${outjar}" basedir="${build}">
      <manifest>
        <!-- Muss angegeben werden damit java die Hauptklasse
             beim Ausfuehren des jars finden kann -->
        <attribute name="Main-Class" value="Print"/>
      </manifest>
    </jar>
  </target>

  <target name="clean"
        description="clean up" >
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${build}"/>
    <delete>
      <fileset dir="." includes="Print*.jar"/>
    </delete>
  </target>
</project>

Ausführung

$ ant
Buildfile: build.xml

init:
    [mkdir] Created dir: /home/dedeibel/proggn/java/test/ant/build
    [mkdir] Created dir: /home/dedeibel/proggn/java/test/ant/build/resources

compile:
    [javac] Compiling 2 source files to /home/dedeibel/proggn/java/test/ant/build

prep:
     [copy] Copying 1 file to /home/dedeibel/proggn/java/test/ant/build/resources
   [delete] Deleting: /home/dedeibel/proggn/java/test/ant/build/resources/file.in

build:
      [jar] Building jar: /home/dedeibel/proggn/java/test/ant/Print-20091030.jar

BUILD SUCCESSFUL
Total time: 2 seconds

Anwendung:

$ java -jar Print-20091030.jar 
Hello.
Content:
A: Build 20091030
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the
B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile.

Quellen

Apache ant homepage
Writing a simple ant file

Die Vernachlässigung von tollen Namespaces für meine Klassen sie mir verziehen.