Während meiner täglichen Arbeit mit Java, stoße ich immer wieder auf seltsame Dinge. So geschehen letztens, als mir die Java NIO API beim Auslesen einer Datei einen Strich durch die Rechnung gemacht hat.

Jar-Archiv

Java Applikationen können als selbstständige Jar-Archive deployed werden. Jar-Archive sind erstmal nichts anderes als normale Zip-Archive, die Ordner, kompilierte Class-Dateien, aber auch alle anderen Arten von Dateien enthalten können. In meinem Fall war es eine einfache Konfigurationsdatei, die ich auslesen wollte.

Zugriff auf Dateien im Jar-Archiv

Die Dateistruktur im Jar-Archiv sieht wie folgt aus:

Dateistruktur im Jar-Archiv

Mit Hilfe der Methoden getResource oder getResourceAsStream des Class-Objektes erhält man Zugriff auf entsprechende Dateien. In diesem Fall ist es Main.class.getResource("test.txt"), das uns eine Instanz der Klasse URL liefert.

Auslesen des Inhaltes mittels Java NIO

Um an den Inhalt der Datei zu gelangen, bietet die Java NIO API zwei interessante Hilfsmethoden an: Files.readAllBytes(Path) (liefert ein byte-Array) und Files.readAllLines(Path) (liefert die Zeilen einer Datei in einer Liste von Strings). Das folgende Codefragment zeigt nun das Auslesen und die Ausgabe der Datei in der Konsole:

URI uri = Main.class.getResource("test.txt").toURI();
Path path = Paths.get(uri);
byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes));

Die URI die hierbei verwendet wird, sieht übrigens wie folgt aus: jar:file:/home/witi/nio-bug-1.0.jar!/test.txt. Man beachte das !, das hinterher noch wichtig wird.

Ansonsten ist der Code ziemlich trivial, nicht wahr? Wäre da nicht die unerwartete FileSystemNotFoundException, die beim Aufruf von Paths.get(uri) geworfen wird:

Exception in thread "main" java.nio.file.FileSystemNotFoundException
    at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
    at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
    at java.nio.file.Paths.get(Paths.java:143)
    at Main.bug(Main.java:25)
    at Main.main(Main.java:11)

Bug oder Feature?

Bei Stack Overflow berichtete ein Benutzer letztes Jahr, dass es ein Bug in der Java API sei und dieser wahrscheinlich erst mit Java 8 behoben sein würde. Er verwies dabei auf die Bug-Einträge 7156873 und 7181278, wo unter Fixed Versions 8 (b36) angegeben wird. In meinem Fall ist Java in Version 1.8.0_11 installiert (build 1.8.0_11-b12), so dass ich davon ausgehe, dass dieser Fix erst in den kommenden Versionen geliefert wird. Sehr erstaunlich bei einem Fehler, der bereits im März 2012 reported worden ist.

Workaround

Einen interessanten Workaround habe ich wiederum bei Stack Overflow gefunden. Hierbei erklärt der Benutzer fge, dass das FileSystem nicht automatisch angelegt werden kann (deswegen die FileSystemNotFoundException) und man dies stattdessen manuell nachholen muss. Basierend auf seiner Lösung sieht der Workaround wie folgt aus:

URI uri = Main.class.getResource("test.txt").toURI();
String[] array = uri.toString().split("!");

try (FileSystem fs = FileSystems.newFileSystem(URI.create(array[0]), Collections.emptyMap())) {
    Path path = fs.getPath(array[1]);
    byte[] bytes = Files.readAllBytes(path);
    System.out.println(new String(bytes));
}

Zur Erinnerung, die URI sah so aus: jar:file:/home/witi/nio-bug-1.0.jar!/test.txt. Zu Beginn muss die URI an der Stelle des ! geteilt werden. Basierend auf dem ersten Teil - bis zum ! - wird ein neues FileSystem erzeugt. Den zweiten Teil - den Dateinamen - nutzen wir, um das Path-Objekt zu erzeugen.

Der Workaround ist natürlich nicht so schön, wie die erwartete Lösung - vor allem, weil man darauf achten muss, das FileSystem wieder zu schließen (Dies übernimmt das try-Konstrukt für uns automatisch), aber sie ist immer noch recht kurz.

Jetzt kann man nur noch hoffen, dass die Oracle-Leute den Bugfix tatsächlich in der nächsten Version liefern werden.

Der gesamte Beispielcode ist in meinem GitHub Repository zu finden.

blogroll
tags