Dienstag, 13. Oktober 2009

Snow Leopard und Rosetta

Schon seit den ersten Mac OS X-Versionen können Java-Applikationen in einer speziellen Verzeichnisstruktur als "normale" Mac OS X-Anwendung verpackt werden, so dass sie u.a. mit einem eigenen Programmsymbol dargestellt werden. Damit solche Application Bundles Java-Bytecode ausführen können, enthalten sie als nativen Mach-O-Startcode im Verzeichnis /Contents/MacOS/ die Datei JavaApplicationStub:

Führt man ältere, noch unter PowerPC (PPC) Mac OS X zusammengebaute Java-Application-Bundles unter Mac OS X 10.6 "Snow Leopard" aus, erhält man eventuell den Hinweis, dass man den auf der Snow-Leopard-Installations-DVD enthaltenen PPC-Emulator "Rosetta" installieren muss, um die Anwendung starten zu können. Obwohl Apple seit Mac OS X 10.4 alte PPC-JavaApplicationStubs erkennt und als 32-Bit-Intel-Prozess neu startet, muss dafür Rosetta installiert sein – und genau das ist seit Snow Leopard nicht mehr standardmäßig der Fall.

Die einfachste Lösung, die zudem Rosetta nicht benötigt, ist, den alten Stub im Application Bundle durch eine Kopie der aktuelle Version /System/Library/Frameworks/JavaVM.framework/Resources/MacOS/JavaApplicationStub zu ersetzen. Dieser neue Stub enthält Code für die drei derzeit unterstützten Prozessorarchitekturen:
Straylight:~ much$ file JavaApplicationStub
JavaApplicationStub: Mach-O universal binary with 3 architectures
JavaApplicationStub (for architecture x86_64): Mach-O 64-bit executable x86_64
JavaApplicationStub (for architecture i386): Mach-O executable i386
JavaApplicationStub (for architecture ppc7400): Mach-O executable ppc
Wenn eine Java-Anwendung eine bestimmte Architektur nicht benötigt (z.B. weil nachgeladene JNI-Bibliotheken dafür nicht compiliert wurden), kann und sollte der entsprechende Code mit /usr/bin/lipo entfernt werden.

Dienstag, 1. September 2009

Java in Mac OS X 10.6 "Snow Leopard"

Der Schneeleopard ist also aus dem Käfig... Und während das nun "alte" Mac OS X 10.5 noch drei Implementierungen für Java 1.4, 5.0 und 6 mitbrachte, enthält Mac OS X 10.6 ausschließlich Java SE 6 (1.6.0_15) in einer 32-Bit- und einer 64-Bit-Version. Detaillierte Informationen zu beseitigten und bekannten Fehlern enthalten die Release Notes.

Damit auch Java 5-Anwendungen weitestgehend problemlos laufen, sind im Ordner /System/Library/Frameworks/JavaVM.framework/Versions noch die Unterordner 1.5 und 1.5.0 enthalten, aber beide sind Aliase (Symlinks) auf die Java 6-Implementierung.

Und obwohl QuickTime for Java schon seit längerem nicht mehr offiziell unterstützt wird, sind die QTJava-Bibliotheken immer noch vorhanden und funktionieren auch mit Java 6.

Mal abwarten, was die praktischen Erfahrungen in den kommenden Tagen zeigen.

Mittwoch, 26. August 2009

Closures in JavaFX Script

JavaFX Script (die Programmiersprache für Suns neue RIA-Technologie JavaFX) ist eine objektorientierte, deklarative und funktionale Programmiersprache.

Als funktionale Sprache unterstützt sie Funktionen höherer Ordnung (first-class functions) und Funktionsabschlüsse (Closures). Ersteres bedeutet, dass Funktionen selber Werte sind – sie können also als Parameter übergeben und zurückgegeben werden. Letzteres erlaubt den Funktionswerten, dass sie sich den Zustand ihrer Umgebung (die lokalen Variablen) bei ihrer Erzeugung merken können.

Für viele sind Closures seltsame Konstrukte und ihre Einsatzmöglichkeiten nicht offensichtlich. Daher im Folgenden ein kleines, relativ leicht verständliches Beispiel, in dem mehrere Zähler mit individuellen Startwerten erzeugt und benutzt werden:
// closure.fx

function zaehlerMitStartwert(startwert: Integer) {
var n = startwert;
return function(): Integer { return n++ }
}

var next1 = zaehlerMitStartwert(10);
var next2 = zaehlerMitStartwert(20);

println( next1() ); // 10
println( next2() ); // 20
println( next2() ); // 21
println( next1() ); // 11
Wer das Beispiel ausprobieren möchte, installiert sich das JavaFX 1.2 SDK und nutzt die Kommandozeile (oder NetBeans oder Eclipse mit dem JavaFX-Plugin):
Straylight:~ much$ javafxc closure.fx
Straylight:~ much$ javafx closure
10
20
21
11
Die ausgegrauten Elemente sind nicht wirklich notwendig und sollen nur das Skript für Java-Programmierer leichter verständlich machen. "return" ist an beiden Stellen optional, da der letzte Ausdruck in einer Funktion gleichzeitig deren Ergebnis definiert. Der Rückgabetyp ":Integer" der inneren, anonymen Funktion ist optional, da aus dem Datentyp des Ergebnisses der Rückgabetyp durch Typinferenz ermittelt werden kann.

Trotz (bzw. gerade wegen) dieser impliziten Typisierung ist JavaFX Script im Gegensatz zu Skriptsprachen wie JavaScript statisch und streng typisiert. Bereits der Compiler ermittelt also die Datentypen und Signaturen von Variablen und Funktionen, sofern sie nicht explizit angegeben sind. Im Beispiel hat die Variable n also den Datentyp Integer, die Variablen next1 und next2 haben den Typ function():Integer und zaehlerMitStartwert hat den Typ function(:Integer):function():Integer :-)

Wie funktioniert das Skript? Beim Aufruf der Funktion zaehlerMitStartwert wird eine Funktion zurückgegeben, die bei jedem Aufruf die Variable n um eins erhöht zurückgibt. Obwohl die Variable n zur äußeren Funktion gehört und damit beim Aufruf der zurückgegebenen Funktion mit next1() bzw. next2() eigentlich gar nicht mehr vorhanden ist, merkt sich die innere Funktion die lokale Variable separat für jede zurückgegebene innere Funktion – und genau das macht sie zur Closure.

Man kann sich Closures als Objekte vorstellen, die aus einer einzigen Methode/Funktion bestehen und wie Objekte zusätzlich einen Zustand haben. Schön sehen kann man dies an den generierten Java-Bytecode-Klassendateien: closure.class und closure$1.class. Die innere, anonyme Funktion wird für die JVM also einfach als innere, anonyme Java-Klasse closure$1.class abgebildet.

Closures sind prima geeignet, um damit z.B. Event-Handler zu definieren, denen man zusätzliche Informationen mitgeben kann (bei Farb-Setz-Buttons in einem Grafikprogramm beispielsweise die zu setzende Farbe). Dadurch kann man häufig auf das Schreiben von Event-Handler-Klassen verzichten, auch wenn dies in JavaFX Script ebenfalls möglich ist.

Vielleicht hat man es gemerkt: Ich halte JavaFX Script trotz des Namens ganz und gar nicht für eine Skriptsprache. Meines Erachtens wurde die Sprache vor allem deshalb so genannt, um zu zeigen, dass man damit in Wettbewerb zu Skriptsprachen wie Ruby tritt, die als agil und produktiv empfunden werden. JavaFX Script zeigt dabei sehr schön, dass sich Produktivität und statische, strenge Typisierung überhaupt nicht ausschließen müssen.

Dienstag, 25. August 2009

Hibernate 3.5 und JPA 2.0

Vor wenigen Tagen wurde die erste Betaversion von Hibernate 3.5 veröffentlicht. Sofern der Download von SourceForge klappt, kann man damit viele der Neuerungen von JPA 2.0 ausprobieren.

Hibernate 3.5 bietet keine wesentlichen neuen Hibernate-Features, und auch für die Unterstützung der neuen JPA-Version wurden vor allem standardkonforme Schnittstellen für bereits vorhandene Hibernate-Eigenschaften bereitgestellt. Dies ist wohl auch der Grund, warum die Hibernate-Version nicht gleich auf 4.0 heraufgesetzt wurde.

Schön ist meines Erachtens, dass ab Version 3.5 die JARs für die Hibernate-Annotationen und den JPA-EntityManager Bestandteil vom Hibernate-Kern (Core) geworden sind. Und die Einbindung von Envers dürfte diese Versionierungs-Bibliothek zum heißen Kandidaten für den De-Facto-Standard bezüglich Versionierung/Historisierung/Auditierung mit JPA machen.

Mittwoch, 22. Juli 2009

Lokaler Aufruf von @Remote in GlassFish

GlassFish muss als Java EE 5-konformer Application-Server beim Aufruf von EJB-@Remote-Interfaces die Übergabeparameter und Rückgabewerte kopieren (serialisieren). Der Aufruf erfolgt also mit Wert-Semantik ("pass-by-value"), selbst wenn der Aufrufer und die aufgerufene Session-Bean innerhalb derselben JVM laufen ("co-located") und der Aufruf damit prinzipiell lokal erfolgen könnte (durch Über-/Rückgabe von Referenzen, entsprechend "pass-by-reference" genannt).

Obwohl GlassFish den eigentlichen Remote-Aufruf bereits gut optimiert, wenn sich Aufrufer und aufgerufene Bean auf demselben Server befinden, kostet das Kopieren der Parameter Zeit und Speicher. Wenn die Parameter große Datenmengen beinhalten, kann es sich daher als Performanz-Optimierung lohnen, die Parameter des lokalen Remote-Aufrufs entgegen der Spezifikation als Referenz übergeben zu lassen. Man muss dann aber bedenken, dass Änderungen an den Parameterdaten – wie bei @Local-Aufrufen – an allen Stellen sichtbar sind, die eine Referenz darauf halten.

Aktivieren lässt sich der lokale Aufruf von @Remote-Methoden in GlassFish für eine komplette Enterprise-Applikation (EAR) im sun-application.xml-Deskriptor wie folgt:
<sun-application>
<pass-by-reference>true</pass-by-reference>
</sun-application>
Oder für einzelne EJBs in sun-ejb-jar.xml:
<sun-ejb-jar>
<enterprise-beans>
<ejb>
<pass-by-reference>true</pass-by-reference>
</ejb>
</enterprise-beans>
</sun-ejb-jar>
Achtung: Zumindest GlassFish 2.1 scheint einen Bug zu haben, dass die Einstellung in sun-application.xml ignoriert wird, wenn sich in einem EJB-Modul (EJB-JAR) kein sun-ejb-jar.xml befindet. Abhilfe: Einfach einen (abgesehen vom Wurzel-Element) leeren Deskriptor ergänzen.

Und nochmal Achtung: Offenbar klappt die Optimierung nur innerhalb einer Anwendung (EAR), nicht aber zwischen mehreren separaten Anwendungen bzw. EJB-Modulen auf demselben Server.

Warum will man überhaupt eine nicht standardkonforme Referenz-Übergabe aktivieren? Kann man nicht besser @Local-Interfaces verwenden, die die erwähnten Performanz-Vorteile sowieso mit sich bringen? Ja, man sollte @Local-Interfaces nach Möglichkeit bevorzugen.
Es gibt aber Fälle, bei denen man nicht schon bei der Programmierung entscheiden kann bzw. möchte, wie die Bean-Aufrufe durchgeführt werden, beispielsweise weil man sich die Option offen halten möchte, ob Frontend (WAR) und Backend (EJB-JAR) gemeinsam in einem EAR oder aber separat bereitgestellt ("deployed") werden. Im Quelltext wird man dann die @Remote-Variante programmieren, hat aber mit dem pass-by-reference-Schalter später per Konfiguration die Möglichkeit, die Aufrufart einfach und schnell umzustellen.

Sinnvoll erscheint dies vor allem dann, wenn man viele EJBs entfernt aufruft und alle Aufrufe auf einen Schlag optimieren will. Wenn man dagegen nur wenige EJBs entfernt nutzt oder nur einzelne Aufrufe optimieren möchte, kann man die Aufrufart – wenn auch mit etwas mehr Aufwand – über die Standard-XML-Deskriptoren ändern. Eine mögliche Realisierung ist hier beschrieben.

Donnerstag, 16. Juli 2009

Sein oder !sein, das ist hier die Frage

Echte C-Freaks werden mich dafür verachten, aber ich finde ausgeschriebene boolesche Operatoren wie not, and und or einfach lesbarer als !, && und || (und ja, ich habe jahrelang C und C++ programmiert und weiß, wovon ich rede). Ein Beispiel:
  if (!liste.isEmpty()) { ... }

if (not(liste.isEmpty())) { ... }
M.E. ist das Ausrufungszeichen beim Durchsehen des Quelltextes viel zu leicht zu übersehen (und Code wird normalerweise viel häufiger gelesen als geschrieben).

Während beispielsweise die EL beide Schreibweisen erlaubt, kennt Java leider nur die Symbol-Operatoren. Was tun? Zumindest für den Präfix-Operator not kann man statische Methoden definieren, die sich dann mittels statischer Imports wie in obigem Beispiel nutzen lassen:
  public static boolean not(boolean bval) {
return !bval;
}

public static Boolean not(Boolean bval) {
if (bval == null) {
return null;
}
else {
return !bval;
}
}
(Den kompletten Quelltext gibt es hier.)

Nur: Muss man sich diese Methoden selber schreiben, oder gibt es dafür bereits fertige Bibliotheken? Im Netz habe ich leider nichts passendes gefunden.

Donnerstag, 9. Juli 2009

java_home oder /Library/Java/Home?

Es ist immer wieder eine spannende Frage, wie man in Mac OS X die Standard-Java-Version einstellt bzw. wie man die für eine bestimmte Anwendung "richtige" Java-Version herausbekommt. Mac OS X 10.5 unterstützt derzeit bis zu drei verschiedene Hauptversionen (1.4.2, 1.5.0, 1.6.0), teils als 32-Bit-, teils als 64-Bit-Variante.

Bisher konnten Anwender die bevorzugte Java-Version getrennt für Applets und für andere Anwendungen (Web Start, Kommandozeile) mit /Programme/Dienstprogramme/Java/Java-Einstellungen konfigurieren. Alternativ konnte man natürlich auch die Umgebungsvariable JAVA_HOME setzen oder sich auf den festen Pfad /Library/Java/Home verlassen. Und weil diese Möglichkeiten immer noch nicht bei allen Anwendungen funktionierten, verbogen die ganz Wagemutigen diverse Symlinks im JavaVM-Framework (was aber auch nicht immer klappte und zu diversen Fehlern führte).

Bevor wir uns um die empfohlene Lösung kümmern, hier zunächst eine Warnung: Basteln Sie niemals an den Symlinks in /System/Library/Frameworks/JavaVM.framework (und in Unterordnern davon) herum! Auch dann nicht, wenn Sie viele gutgemeinte Tipps finden, wie man Current und CurrentJDK verbiegen kann... Apple weist seit langem darauf hin, dass diese Symlinks interne Implementationsdetails sind, die sich jederzeit ändern können.

Mit dem aktuellen Java-Update für Mac OS X 10.5 liefert Apple nun ein Kommandozeilenprogramm aus, mit dem sich die Benutzereinstellungen aus /Programme/Dienstprogramme/Java-Einstellungen auslesen lassen (beachten Sie, dass der Java-Unterordner in dem Pfad mit diesem Update verschwunden ist):

HeartOfGold:~ much$ /usr/libexec/java_home
/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home

Ein Aufruf mit der Option -h zeigt alle erlaubten Optionen an. Eine Liste aller verfügbaren JVMs – in der in den Java-Einstellungen festgelegten Reihenfolge – erhalten wir mit --verbose:

HeartOfGold:~ much$ /usr/libexec/java_home --verbose
Matching Java Virtual Machines (4):
1.5.0_19 (i386): /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
1.6.0_13 (x86_64): /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
1.5.0_19 (x86_64): /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
1.4.2_21 (i386): /System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home

Man kann auch ganz gezielt nach einer bestimmten Java-Version und/oder Prozessorarchitektur fragen:

HeartOfGold:~ much$ /usr/libexec/java_home --version 1.6 --arch x86_64
/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home

Sofern die gewünschte JVM nicht vorhanden ist, erhält man eine Fehlermeldung und eine hoffentliche passende alternative JVM:

HeartOfGold:~ much$ /usr/libexec/java_home --version 1.6 --arch i386
Unable to find any JVMs matching architecture "i386".
/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home


Zurück zur Ausgangsfrage. Wie finden wir damit z.B. in einem Shell-Skript die passende Java-Version für eine Anwendung? Apple empfiehlt folgendes Vorgehen:
  1. Prüfen Sie, ob /usr/libexec/java_home vorhanden ist, übergeben Sie optional die gewünschte Java-Version und werten Sie die Rückgabe aus.
  2. Wenn das nicht erfolgreich war, verwenden Sie als Java-Home-Verzeichnis fest kodiert (!) das Verzeichnis /System/Library/Frameworks/JavaVM.framework/Versions/1.X/Home, wobei Sie "1.X" durch die gewünschte Versionsnummer (1.4, 1.5, 1.6) ersetzen.
  3. Sollte das Verzeichnis nicht existieren, verwenden Sie /Library/Java/Home (und beten, das alles klappt).
100% perfekt ist das m.E. zwar nicht, denn Punkt 2 geht schief, wenn das Verzeichnis für Java 6 existiert, Java 6 aber nicht ausgeführt werden kann (das betrifft vor allem 32-Bit CoreDuo-Macs). Aber immerhin hat Apple nun dokumentiert, wie man künftig – und in den meisten Fällen auch bisher – zum korrekten Ergebnis gelangt.


Und was ist mit der Umgebungsvariable JAVA_HOME? Offenbar werten diverse Systemprogramme (wie /usr/bin/java) nicht nur die Rückgabe von /usr/libexec/java_home aus, sondern auch den Wert von JAVA_HOME. Wenn sich die Werte unterscheiden, kann das zu unerwartetem Verhalten führen... Insofern sollte mittelfristig auf das Setzen von JAVA_HOME besser verzichtet werden.

Da sich aber kurzfristig viele Anwendungen noch auf die Umgebungsvariable JAVA_HOME verlassen, ist es derzeit m.E. am besten, die Variable auf die Java-Version zu setzen, die auch in den Java-Einstellungen an oberster Stelle steht (auch wenn das erst einmal doppelten Konfigurationsaufwand bedeutet). Das Setzen der Variablen geschieht am einfachsten mit RCEnvironment, wodurch die Variable sowohl Shell-Skripten als auch GUI-Anwendungen bekannt ist:

Eine ausführliche Diskussion rund um java_home findet sich auf Apples Java-Mailingliste.