Clojure store persistent data structures gotcha – load-file size limit

In clojure – the data structure and their default representation are suited as readable and portable format to persist data. But there is a small pitfall in using it.

Data Structure example:

{
  :request-time 2636,
  :status 200,
  :headers
  {
    "server" "Server",
    "content-encoding" "gzip",
  }
}

Be aware that this function is intended to load code only. If your data structures or a string in them grow bigger than around 65,535 it crashes.

Exception similar to:

java.lang.ClassFormatError: Unknown constant tag 49 in class file parse$eval13

Please use read-string instead.

Example: (read-string (slurp "data.clj"))

Source: Rich Hickey on google groups Google Groups

Clojure example from „The Joy Of Clojure“ parallelized

I am currently reading „The Joy Of Clojure“ by Michael Fogus and Chris Houser – a great book to see the „why“ behind the clojure way. You should have had a look into clojure before and be willing to read a lot of docs besides the book.

In the book there is a simple example program to display a nice graphic, created by applying xor to the pixel coordinates and using them as grayscale color value. The chapter is „Using the REPL to experiment“.
While reading this part, I thought it would be nice to have that algorithm for parallel processing and experimented a little bit.

The following listing is the result, it modified the original and uses simple threading and the „locking“ macro. It is also interesting to see what happens if you leave out the synchronizing between setting the color and drawing the pixel, as you can see in the screenshots. I hope the original author is okay with using his examples.

Simply paste it to your REPL and try it out.

(defn
  ^{:doc "Calculate the color for a coordinate x and y by applying the given
function. Offset and max values can be specified as input." }
  f-valueso [f xoffset xmax yoffset ymax]
  (for [x (range xoffset xmax) y (range yoffset ymax)
          :let [v (rem (f x y) 256)]]
    [x y (if (< v 0) (* -1 v) v)]
  )
)

(defn
  ^{:doc "Devides the given value in 'parts' equal parts. For
example 200 2 will become [[0 100] [100 200]]"}
  from-to-parts [value parts]
  (loop [
    steps (conj (vec (range 0 value (int (/ value parts)))) value)
    result []
    ]
    (if (seq (rest steps))
      (recur (rest steps) (conj result [(first steps) (second steps)]))
      result
    )
  )
)

; Initialize the frame
(def frame (java.awt.Frame.))
(.setVisible frame true)
(.setSize frame (java.awt.Dimension. 256 256))
(.setLocation frame 300 300)
; Graphics size is only as big as the frame was at getGraphics times
; (that had cost me some time ...)
(def gfx (.getGraphics frame))

(defn clear
  ([g] (.clearRect g 0 0 256 256))
  ([g x y] (.clearRect g 0 0 x y))
)

(defn
  ^{:doc "Draw the picture in a specified area using the color function."}
  draw-part [gfx f xoffset xmax yoffset ymax]
  (doseq [[x y v] (f-valueso f xoffset xmax yoffset ymax)]
;    (Thread/sleep 1) Uncomment this if it draws too fast
    (locking gfx
      (.setColor gfx (java.awt.Color. v v v))
      (.fillRect gfx x y 1 1)
    )
  )
)

(defn
    ^{:doc "Draw a picture to 'gfx' that is created by using the
given color function. Use 'n' threads to create the picture from
0 to x/y max"}
  draw-valuesp [f xmax ymax n]
  (clear gfx)
  (.setSize frame (java.awt.Dimension. xmax ymax))
  (def gfx (.getGraphics frame))
  (clear gfx xmax ymax)
  (doseq [
    [xoffset xmax] (from-to-parts xmax n)
    [yoffset ymax] (from-to-parts ymax n)
  ]
    (.start (Thread. 
      #(draw-part gfx f xoffset xmax yoffset ymax)
    ))
  )
)

(draw-valuesp bit-xor 256 256 3)

The result:

Multithreaded version during processing:

Result without synchronization:

I am aware that there is not much concurrency in the problem and it is not the greatest example to show the power of clojure’s capabilities. But that wasn’t the point of it.

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.

Eclipse Copy / Cut und Paste einer ganzen Zeile

Jeder vim Nutzer, den es irgendwann doch zu Eclipse verschlagen hat, wird sicherlich die elegante und einfache Art, eine ganze Zeile mittels „yy“ und „p“ zu kopieren, vermissen.

Nach einer kurzen Suche bin ich dann auf eine kleine Erweiterung gestoßen, die dies ermöglicht. copycutcurrentline (google code)

What is it?

1. Press Ctrl-C to copy or Ctrl-X to cut the whole line in an editor if you select nothing. (It works as usual if you select some text.)
2. Put the caret on any line on any position.
3. Press Ctrl-V to paste the line above.

Works on Windows and Linux. Should work on Mac but not tested. Tested on Eclipse 3.4, 3.5 and 3.6 on Java 6.

Unter meinem Eclipse Helios und JEE Developer hat es wunderbar funktioniert. Dank an den Entwickler!

Am Rande: Wer es noch nicht kennt, MoreUnit (sf.net) ist ein Tool zum einfachen Wechsel zwischen Klassen und Unit-Tests. Zudem markiert es Klassen, die über Tests verfügen und gibt die Möglichkeit Test-Methoden-Stubs anzulegen.

Annoyme milestone 0.1.0 erreicht

Ich hatte endlich mal wieder ein wenig Zeit und habe beschlossen, dass annoyme den im Wiki definierten milestone 0.1.0 erreicht hat.

Die Änderungen belaufen sich allerdings hauptsächlich auf organisatorische und Paketier-Themen. So war der Milestone definiert als Packaging, documentation, source-forge setup, womit als nächstes dem Ausbau neuer Features nichts mehr im Wege steht.
Die Anwendung ist nun mittels cpack paketierbar und das Ergebnis auch auf der SF Seite verfügbar. Die Projektseite ist https://sourceforge.net/projects/annoyme/ und der Link zum Binary https://sourceforge.net/projects/annoyme/files/

Der Code ist in Github unter dem Tag milestone-0.1.0 oder auf der SF Seite als snapshot verfügbar.

So Wahr: Whatever Happened To Programming

Programmieren ist nicht mehr das was es einmal war, in Zeiten der Frameworks und großen Bibliotheken fühlt es sich mehr wie ein Konfigurieren an. Wenn man damit umgehen kann, kommt man zwar schneller zu einem Ergebnis aber für viele ist das Ergebnis nicht das wichtigste sondern der Spaß am Programmieren. Der folgende Artikel handelt davon.

http://bit.ly/wordpress_reprog_whatever_happened_to_programming

Sicherlich ist es nicht sinnvoll und auch nicht interessant immer die selben Methoden zu implementieren. So kommt man auch nicht vorwärts. Aber da muss noch etwas zwischen dem Rad-Neu-Erfinden und dem Herum-ärgern mit Frameworks, was zu einer Trial-And-Error Aktion ausarten kann, sein.

Ü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

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.

Groovy Methodenauswahl Gotcha

Groovy hat mich schon wieder erwischt. Da wollte ich doch nur eine Methode aufrufen die wahlweise einen String oder ein Array von Strings erwartet, jedoch war das Ergebnis eher überraschend.

Man nehme:

public class CallString {
private String[] books = new String[64]
public void setBook(String book) {
System.out.println(„set book string“)
this.books[0] = book
}
public void setBook(String[] inBooks) {
System.out.println(„set book array“)
for (int i = 0; i < inBooks.length && books.length; ++i) { this.books[i] = inBooks[i] } } public String toString() { return "["+ books.findAll {it != null}.join(", ") +"]" } } [/sourcecode] Und rufe folgendes auf: [sourcecode language='java'] CallString o = new CallString(); o.setBook("The Book") println o $ groovy call.groovy set book string [, The Book] [/sourcecode] Okay das ist noch wie erwartet. Allerdings ist der Mensch ja faul und verwendet gerne die abgekürzte schreibweise: [sourcecode language='java'] CallString o = new CallString(); o.book = "The Book" println o $ groovy call.groovy set book array [, T, h, e, , B, o, o, k] [/sourcecode] Das ist nun wirklich nicht das was ich erwartet habe und hat mich auch ein wenig Zeit gekostet. Das selbe passiert übrigens auch wenn CallString in Java geschrieben ist.

Wie dem auch sei scheint es wie auch bereits beim vorherigen Groovy Map Gotcha mit der Magie hinter dem Methodenzugriff per „.“ zu stecken und man sollte direkt die gewünschten Methoden aufrufen wenn man auf der sicheren Seite sein will – oder muss wissen was man tut.