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.

QBasic on Linux

Mich hat es heute irgendwie gepackt und ich habe meine alten Jugendsünden wieder einmal herausgekramt. BASIC heißt das Zauberwort.

Früher auf dem 486er, den ich von meinem Onkel hatte (vielen Dank an dieser Stelle noch einmal, wer weiß ob mich der PC und das Programmieren ohne dich jemals so gefessel hätte), habe ich sehr viel mit QBasic herumgespielt, ohne ein Fünkchen Ahnung davon zu haben. Es war sehr spannend was man alles mit ein paar Zeilen Text machen konnte, es machte Spaß bestehende Programme zu analysieren, die Hilfe-Funktion zu durchforsten, wilde, durch alle Farben iterierende Schleifen zu bauen. Dass ich dabei den furchtbarsten Code meines Lebens produziert habe, war damals wohl egal.

Ich bin heute jedoch froh, dass ich diese Relikte noch aufgehoben habe, samt einer Kopie von QBasic.

Mit Hilfe von dosemu bzw. xdosemu kann man das Programm (qb.exe) nämlich noch wunderbar starten und seine alten Machwerke bestauenen. Vielleicht führe ich mein großes Spieleprojekt „Hunt“ einmal zu Ende … im Rentenalter.

Freebasic habe ich auch probiert zu verwenden, es war auf die schnelle aber nicht auf AMD 64 zum laufen zu bringen, 32Bit X Libs gefehlt und den Code habe ich nur als BASIC Code gefunden …

Anbei ein paar Screenshots von qb.exe auf dosemu.
(Für irgendwelche Schmerzen, geistiger oder körperliche Art dir durch den Anblick des Codes entstehen könnten übernehme ich keinerlei Haftung.)

MySQL GROUP BY Gotcha

Ich will hier kurz ein Gotcha vorstellen, das bei der Verwendung von Group By unter MySQL auftreten kann und dann ein Gegenbeispiel in PostgreSQL bringen.

Nehmen wir folgende Tabelle an:


O_Id OrderDate OrderPrice Customer
1 2008/11/12 1000 Hansen
2 2008/10/23 1600 Nilsen
3 2008/09/02 700 Hansen
4 2008/09/03 300 Hansen
5 2008/08/30 2000 Jensen
6 2008/10/04 100 Nilsen

Dann wäre das folgende Query gültig und hilfreich um die Summe für einen Kunden zu bestimmen:


$ SELECT Customer,SUM(OrderPrice) FROM Orders GROUP BY Customer
Customer SUM(OrderPrice)
Hansen 2000
Nilsen 1700
Jensen 2000

Hierbei befinden sich alle angezeigten Felder in dem GROUP BY Ausdruck oder in einer Aggregatfunktion.

Unter MySQL ist jedoch auch folgendes möglich:


SELECT * FROM Orders GROUP BY Customer

Hierbei würde nach Kunde Gruppiert, sowie alle anderen Felder angezeigt werden. Allerdings ist hierbei *nicht definiert welche Werte die nicht gruppierten Felder haben!* Siehe dazu auch MYSQL: GROUP BY mit versteckten Feldern. Hier wird dies „Feature“ genannt und muss explizit deaktiviert werden …

Unter PostgreSQL bekommt man jedoch standardmäßig folgende Fehlermeldung die einem vor so manchem Fehler bewahren kann:


$ SELECT * FROM Orders GROUP BY Customer;
ERROR: column "Orders.O_Id" must appear in the GROUP BY clause or be used in an aggregate function

Quellen:

Deploy the Spring Security Tutorial

Ich arbeite mich zur Zeit ein wenig in verschiedene Sicherheitsframeworks für Java ein und bin gerade dabei Spring Security genauer unter die Lupe zu nehmen.

Allerdings gab es bereits Probleme beim Deployen der Beispielanwendung aus dem Quick Start Guide.

Der Tomcat Application Manager gab nachdem Installieren nur ein sehr hilfreiches: FEHLER – Anwendung mit Kontext Pfad /tutorial konnte nicht gestartet werden von sich.

Das Systemlog führte einen dann schon eher auf die richtige Fährte:

[ERROR,[/tutorial]] Exception sending context initialized event to listener instance of class org.springframework.web.util.Log4jConfigListener java.security.AccessControlException: access denied (java.util.PropertyPermission tutorial.root read)
...
SEVERE: Error listenerStart
SEVERE: Context [/tutorial] startup failed due to previous errors

Interessanterweise landete die oberste Meldung nicht in den Tomcat Logs sondern nur in der /var/log/syslog. Die SEVERE Meldungen sind auch in den Tomcat Logs zu finden aber eben nicht annähernd so hilfreich.

Um nun nicht die ganze Beispielanwendung untersuchen und die einzelnen Regeln definieren zu müssen, reicht es für Testzwecke aus die Sicherheitsprüfung im Tomcat zu deaktivieren. Für den 5.5er Tomcat untern Debian, wird dies in der /etc/default/tomcat5.5 mit dem Eintrag TOMCAT5_SECURITY=no erreicht.

hth

GP2X revival

Ich habe mir endlich mal wieder eine neue SD Karte geholt und die Chance genutzt um meinen guten alten GP2X F100 MK1 auszupacken. Damals hatte ich mir gleich einen aus den ersten Fuhren ergattert und habe mich schon riesig gefreut Emulatoren zu zocken, Videos zu schauen und selber Anwendungen zu schreiben.

Nunja an Anwendungen ist dann nicht sooo viel dabei heraus gekommen, ich habe eine Version von Game of Life für den GP2X geschrieben, sowie eine Version von einem Rubik’s Cube in 3D in der Mache. Dieser ist allerdings noch nicht fertig. Mal sehen ob das überhaupt noch was wird.

Infos über den GP2X gibt es auf der deutschen Portalseite gp2x.de. Wichtige Anlaufpunkte sind hierbei das Forum, Wiki und das Dateiarchiv.

GP2X of Life

GP2X of Life in git
GP2X of Life im GP2X Archiv
Eine Version von Game of Life für den GP2X handheld. Mit der SDL auch unter Linux ausführbar.
GP2X of life screenshot

Man sollte jedoch nicht verheimlichen dass das gerät mittlerweile schon einige Jahre auf dem Buckel hat und es demnächst die Pandora gibt. Mit WLAN und allem Schnickschnack. :-)

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.

Groovy Map Gotcha

Ich schaue mir derzeit Groovy an und bin dabei auf etwas gestoßen was mich doch ziemlich verwirrt hat. Es geht um das Verhalten des .class Attributs (?) von Objekten (Wobei sich dies hinterher als „Feature“ der groovy Maps heraugestellt hat).


groovy:000> [1, 2, 3].class
===> class java.util.ArrayList
// Wie erwartet


groovy:000> [a: 1, b: 2, c: 3].class
===> null
// WTF?


groovy:000> [a: 1, b: 2, c: 3].getClass()
===> class java.util.LinkedHashMap
// Hmm, das scheint aber zu funktionieren

Die Lösung dieses Mysteriums hat sich mir dann erschlossen als ich herausgefunden habe dass man auf Maps mittels Schreibweise: mymap.key zugreifen kann.


groovy:000> [a: 1, b: 2, class: 3].class
===> 3
// Was sonst ... :)

Wir lernen also:

  1. Benutze getClass()
  2. Magie hat nicht nur die Leute im Mittelalter verwirrt
  3. Groovy hat einige Gotchas

Annoyme Portierung auf Mac OS X

Ich hatte mich schon immer auf eine Gelegenheit gefreut mal etwas bestehendes auf eine andere Platform zu portieren. Ich hab zwar schon unter Solarix und AIX gecodet aber nun hatte ich eben die Chance mein eigenes Annoyme auf OSX zu testen und war angenehm überrascht. Bis auf ein paar Probleme mit Includes, Anpassungen der cmake Skripte und Dateinamenprobleme hat es ganz gut geklappt.

Interessant war jedoch auch ein Problem mit libao unter 10.4 siehe: https://trac.xiph.org/ticket/727

Laufen tut annoyme jedoch noch nicht ganz auf Mac, die libao Version kann hier offenbar nur mit 44k Samples umgehen und bei annoyme gibts zur Zeit nur 22k.


./annoyme
...
Opening sound output.
ao_macosx_open: Only support 44.1kHz right now
terminate called after throwing an instance of 'SoundOutputException'

Ingesamt würde ich auch sagen ist dies ein ziemlich guter Test für cmake gewesen von dem ich recht begeistert bin. Im gegensatz zu den autotools war der Einstieg auch recht einfach auch wenn man beim Einstieg vom ersten Howto eigentlich direkt in die Funktionsreferenz gehen muss da man ansonsten nur auf das Buch verwiesen wird und andere Einleitungsdokumente fehlen. http://www.cmake.org/

Mac – Case Insensitivity

Ein weiterer Fallstrick der mich bei meiner spielerei mit dem Mac überraschte war die offensichtliche Case Insensitivtät unter OSX.

Als jemand der sich unter der Shell relativ wohl fühlt ist dies ein druchaus überraschendes Verhalten. Mir ist es jedoch erst aufgefallen, alls ich etwas aus dem Annoyme git Repository ausgecheckt habe und seltsamerweise immer eine Datei verändert war.

Im Repository gibt es eine Datei „annoyme.cpp“, welche main beinhaltet und „Annyome.cpp“ für die Hauptklasse der Anwendung. Anscheinend muss ich mir hier aus portibilitätsgründen etwas neues ausdenken.

Hier ein kleines Beispiel wie sich die Case Insensitivität auf der Shell auswirken kann:

/tmp/testrange)$ ls -l
/tmp/testrange)$ touch thesame
/tmp/testrange)$ ls -l
total 0
-rw-r--r-- 1 bpeter wheel 0B 5 Apr 21:37 thesame
/tmp/testrange)$ touch TheSame
/tmp/testrange)$ ls -l
total 0
-rw-r--r-- 1 bpeter wheel 0B 5 Apr 21:38 thesame
/tmp/testrange)$ rm tHeSaMe
/tmp/testrange)$ ls -l