Java Getriebe

Java und NetBeans

Start-, Programm- und Arbeitsverzeichnis

Ich wenn sie immer mehr an Gewicht verlieren je mehr man mit grafischen Oberflächen arbeitet bereit meiner Erfahrung nach gerade Programmieranfängern der Umgang mit (Verzeichnis-)Pfaden immer wieder Schwierigkeiten. Damit will ich nicht sagen, dass sie nicht wissen, was ein Verzeichnis ist, sondern eher die Ungewissheit in welchem Verzeichnis sie denn gerade „arbeiten“. Der Unterschied zwischen dem Start-, Programm- und Arbeitsverzeichnis ist den meisten nicht klar.

Tatsächlich ist es auch so, dass diese drei „unterschiedlichen“ Verzeichnisse bei einem simplen „Doppelklick“ im Windows Explorer auf eine .exe Datei in der Tat auf das gleiche Verzeichnis verweisen. Aber was ist denn jetzt der Unterschied?

Fangen wir mit dem offensichtlichen an. Das Programmverzeichnis ist genau dass, was der Name vermuten lässt. In diesem Verzeichnis befindet sich das Programm selbst. Unter Windows ist diese die .exe Datei, unter den Unix Derivaten die Datei mit dem executable flag. Für Java Programme könnte man sich jetzt streiten. Aus Sicht des Betriebssystems ist das Programm sicherlich die Java VM. Für selbige ist das Programm aber die .jar Datei. Ich für meinen Teil halte mich auch an die Sicht der VM. Also da, wo meine .jar Datei liegt, die ich starte, da ist mein Programmverzeichnis.

Genau genommen ist das Arbeitsverzeichnis kein „automatisches“ Verzeichnis und nicht wirklich festgelegt. Wie der Name schon sagt, soll in diesem Verzeichnis „gearbeitet“ werden. Also Dateien gelesen und geschrieben werden. Man könnte auch sagen, dass das Arbeitsverzeichnis für jede Datei anders ist. nämlich genau das Verzeichnis in dem die Datei liegt (oder abgelegt wird). Betrachtet man einmal die Konstruktoren der File Klasse findet man diese Definition recht einfach wieder. Der Parameter parent könnte man als dieses Arbeitsverzeichnis betrachten (wenn man den Parameter child ohne weitere Pfadangabe angibt). Gibt man dem String Konstruktor nur einen Dateinamen mit, so gilt das aktuelle Verzeichnis als Arbeitsverzeichnis für diese Datei.

Dieses aktuelle Verzeichnis ist zu Programmbeginn gleichbedeutend mit dem Startverzeichnis der Anwendung. Das Startverzeichnis ist also nichts anderes, als jenes Verzeichnis, welches beim Starten des Programms das aktuelle ist. Bei einem Doppelklick auf eine .jar unter Windows wird das aktuelle Verzeichnis auf das gleiche Verzeichnis gesetzt, wie das Programmverzeichnis. Aber die Befehlszeile

1
c:\temp>java.exe -jar c:\projekte\projekt.jar

sorgt dafür, dass die beiden unterschiedliche Werte besitzen. Das Programmverzeichnis ist c:\projekte wo hingegen das Startverzeichnis c:\temp ist.

Wie ermittle ich die Verzeichnisse zur Laufzeit?

In Java kann man das aktuelle Verzeichnis als einziges auf einfache Art und Weise auslesen und verändern. Die System Property „user.dir“ beinhaltet zu jeder Zeit besagtes Verzeichnis. Zum Programmstart ist hier also das Startverzeichnis gespeichert. Möchte man zu einem späteren Zeitpunkt das Startverzeichnis noch einmal verwenden sollte man sich den Inhalt des Properties zwischenspeichern

1
2
3
4
5
6
7
8
9
10
import java.io.File;

public class Start {
  public static File startdir;
  public static void main(String[] args) {
    String userdir = System.getProperty("user.dir");
    startdir = new File(userdir);
    // more stuff
  }
}

Der Inhalt der Variable ist unter normalen Umständen immer ein absoluter Pfad.

Komplizierter wird es schon beim Programmverzeichnis. In C Programmen wird das Programmverzeichnis mehr oder weniger direkt über das args Feld der main Methode übergeben. Dieses Konzept funktioniert unter Java allerdings nicht und ist im Konzept der VM und der Art und Weise wie .class Dateien zur Laufzeit geladen werden geschuldet. Man muss sich eines kleinen Tricks bedienen, welcher aber auch nicht unter allen Umständen funktioniert. Er klappt nur, wenn die Hauptklasse innerhalb einer .jar Datei abgelegt ist. Werden .class Dateien direkt im Dateisystem gestartet (z.B. im Debugger) klappt die folgende Klasse nicht. Aber in diesen Fällen kann man zu 90% auch das Startverzeichnis verwenden oder aber man weiß eh, was man tut.

Hauptakteur zur Ermittlung des Programmverzeichnisses ist die URL Klasse und die Fähigkeit eines Class Objekts die URL einer bestimmten Ressource zu ermitteln. Diese Ressource kann natürlich auch eine .class oder genauer die .class Ressource der Startklasse sein. Über diese URL lässt sich die „umgebende“ .jar Datei ermitteln und darüber dann das Programmverzeichnis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Manager for start- and application directories.
 *
 * @author nigjo
 */

public class DirectoryManager {
  public static File startdir;
  public static File appdir;

  public static void init(Class<?> mainClass) {
    String userdir = System.getProperty("user.dir");
    startdir = new File(userdir);

    String mainResName = mainClass.getSimpleName()+".class";
    URL mainRes = mainClass.getResource(mainResName);
    if(!mainRes.getProtocol().equals("jar")) {
      // don't know what to do. use startdir.
      appdir = startdir;
      return;
    }
    String mainLocator = mainRes.getPath();
    String jarpart = mainLocator.substring(0,
        mainLocator.lastIndexOf("!/"));
    File jarfile;
    try
    {
      jarfile = new File(new URI(jarpart));
      appdir = jarfile.getParentFile();
    }
    catch(URISyntaxException ex)
    {
      // should not happen. actually.
      Logger.getLogger(DirectoryManager.class.getName()).log(
          Level.SEVERE, null, ex);
      appdir = startdir;
    }
  }
}

Die entscheidende Zauberei passiert in Zeile 22/23. Hier wird der Teil der Ressourcenadresse abgetrennt, der „innerhalb“ der .jar Datei liegt. In diesem Fall der Klassenname. Eine „jar:“ URL trennt den Namen der .jar Datei vom Inhalt des Archivs immer durch die Zeichenfolge „!/“ ((Aber Vorsicht: Das Ausrufezeichen ist ein durchaus erlaubtes Zeichen für einen Verzeichnis- oder Dateinamen. Sollte also ein Pfad oder Dateiname mit einem Ausrufezeichen Enden muss noch einmal besonders geprüft werden. Die Sicherste Methode ist von hinten so lange zu suchen, bis File.exists() true zurückliefert.)). Da der String noch eventuell URL-Codierte Zeichen enthält darf der String nicht direkt an ein File Objekt übergeben werden sondern muss noch einmal durch einen „URI-Filter“. Leider erfordert dies einen try-catch Block, der allerdings in dieser „definierten“ Umgebung niemals ausgelöst werden sollte.

In der main Methode muss jetzt nur noch der Aufruf für die init-Methode eingetragen werden und schon kann man im weiteren Programmverlauf jederzeit auf Start- und Programmverzeichnis zugreifen.

1
2
3
4
5
  public static void main(String[] args)
  {
    DirectoryManager.init(Main.class);
    // more stuff
  }

ps: Noch schöner könnte man es machen, wenn man statt der public Felder „Getter“ Methoden implementieren würde.