Implementierungsmöglichkeiten für die Klasse File

Wir sollten für die Klasse File noch einmal festhalten, dass sie lediglich ein Verzeichnis oder eine Datei im Verzeichnissystem repräsentiert, doch keine Möglichkeit bietet, auf die Daten selbst zuzugreifen. Es ist ebenso offensichtlich, dass wir mit den plattformunabhängigen Eigenschaften von Java spätestens bei Zugriffen auf das Dateisystem nicht mehr weiterkommen. Wenn zum Beispiel eine Datei gelöscht werden soll oder wir eine Liste von Dateien im Verzeichnis erbitten, sind Betriebssystemfunktionen mit von der Partie. Diese sind dann nativ programmiert. Es bieten sich zwei Möglichkeiten für die Implementierung an.

Zunächst ist es denkbar, die nativen Methoden in der Klasse File selbst anzugeben. Diesen Weg ging die Sun-Implementierung eine gewisse Zeit lang, und Kaffe nutzt diese Variante heute noch. Doch die Verschmelzung von einem Dateisystem in der Klasse File hat auch Nachteile. Was, wenn das Dateisystem eine Datenbank ist, sodass die typischen nativen C-Funktionen unpassend sind?

Aus dieser Beschränkung heraus hat sich Sun entschlossen, eine nur paketsichtbare, abstrakte Klasse FileSystem einzufügen, die ein abstraktes Dateisystem repräsentiert.

Jedes Betriebssystem implementiert folglich eine konkrete Unterklasse für FileSystem, die dann von File genutzt werden kann. File ist nun völlig frei von nativen Methoden und leitet alles an das FileSystem-Objekt weiter, das intern mit folgender Zeile angelegt wird:

static private FileSystem fs = FileSystem.getFileSystem();

Dann ergibt sich zum Beispiel für den Zugriff auf die Länge einer Datei:

public long length() {
SecurityManager security = System.getSecurityManager();
if ( security != null )
security.checkRead( path );
return fs.getLength( this );
}

Wenn wir dies noch einmal mit dem ersten Weg vergleichen, finden wir in der File-Implementierung von Kaffe etwa Folgendes:

public class File implements Serializable, Comparable
{
static {
System.loadLibrary( "io" );
}

public long length() {
checkReadAccess();
return length0();
}

native private long length0();
}

Die native Methode ist selbstverständlich privat, da es sonst ja keine Sicherheitsprüfung gäbe. Oft trägt sie die Endung 0, sodass eine Unterscheidung einfach ist. Um das Geheimnis um die native Methode length0() zu lüften und einen Eindruck von nativen Methoden zu vermitteln, gönnen wir uns einen Blick auf die Implementierung:

jlong java_io_File_length0( struct Hjava_io_File* this )
{
struct stat buf;
char str[MAXPATHLEN];
int r;
stringJava2CBuf( unhand(this)->path, str, sizeof(str) );
r = KSTAT( str, &buf );
if ( r != 0 )
return (jlong)0;
return (jlong)buf.st_size;
}

Der Aufruf stringJava2CBuf() konvertiert die Unicode-Zeichenfolge in eine gültige C Zeichenkette, die mit einem Null-Byte abschließt. Es folgt der Aufruf einer ANSI-Bibliotheksfunktion, die noch über KSTAT gekapselt ist. Der Datentyp jlong ist kein C(++)-Datentyp, sondern in der javaspezifischen Header-Datei definiert.

Wenn wir uns etwas später mit dem Zugriff über Stream-Klassen beschäftigen, dann benutzt auch diese Klasse native Methoden und eine abstrakte Repräsentation eines Datei-Deskriptors.