11.9 Benutzereinstellungen *
Einstellungen des Benutzers – wie die letzten vier geöffneten Dateien oder die Position eines Fensters – müssen abgespeichert und erfragt werden können. Dafür bietet Java eine Reihe von Möglichkeiten. Sie unterscheiden sich unter anderem in dem Punkt, ob die Daten lokal beim Benutzer oder zentral auf einem Server abgelegt sind.
Im lokalen Fall lassen sich die Einstellungen zum Beispiel in einer Datei speichern. Das Dateiformat kann in Textform oder binär sein. In Textform lassen sich die Informationen etwa in der Form Schlüssel=Wert oder im XML-Format ablegen. Welche Unterstützung Java in diesem Punkt gibt, zeigen die Properties-Klasse (siehe Kapitel 13, »Einführung in Datenstrukturen und Algorithmen«) und die XML-Fähigkeiten der Java-API (siehe Kapitel 16, »Die Einführung in die <XML>-Verarbeitung mit Java). Werden Datenstrukturen mit den Benutzereinstellungen serialisiert, kommen in der Regel binäre Dateien heraus. Unter Windows gibt es eine andere Möglichkeit der Speicherung: die Registry. Auch sie ist eine lokale Datei, nur kann das Java-Programm keinen direkten Zugriff auf die Datei vornehmen, sondern muss über Betriebssystemaufrufe Werte einfügen und erfragen.
Sollen die Daten nicht auf dem Benutzerrechner abgelegt werden, sondern zentral auf einem Server, so gibt es auch verschiedene Standards. Die Daten können zum Beispiel über einen Verzeichnisdienst oder Namensdienst verwaltet werden. Bekanntere Dienste sind hier LDAP oder Active Directory. Zum Zugriff auf die Dienste lässt sich das Java Naming and Directory Interface (JNDI) einsetzen. Natürlich können die Daten auch in einer ganz normalen Datenbank stehen, auf die dann die eingebaute JDBC-API Zugriff gewährt. Bei den letzten beiden Formen können die Daten auch lokal vorliegen, denn eine Datenbank oder ein Server, der über JDNI zugänglich ist, kann auch lokal sein. Der Vorteil von nicht-lokalen Servern ist einfach der, dass sich der Benutzer flexibler bewegen kann und immer Zugriff auf seine Daten hat.
Zu guter Letzt lassen sich Einstellungen auch auf der Kommandozeile übergeben. Das lässt die Option –D auf der Kommandozeile zu, wenn das Dienstprogramm java die JVM startet. Nur lassen sich dann die Daten nicht einfach vom Programm ändern, aber zumindest lassen sich so sehr einfach Daten an das Java-Programm übertragen.
11.9.1 Benutzereinstellungen mit der Preferences-API

Mit der Klasse java.util.prefs.Preferences können Konfigurationsdateien gespeichert und abgefragt werden. Für die Benutzereinstellungen stehen zwei Gruppen zur Verfügung: die Benutzerumgebung und die Systemumgebung. Die Benutzerumgebung ist individuell für jeden Benutzer (jeder Benutzer hat andere Dateien zum letzten Mal geöffnet), aber die Systemumgebung ist global für alle Benutzer. Je nach Betriebssystem verwendet die Preferences-Implementierung unterschiedliche Speichervarianten und Orte:
- Unter Windows wird dazu ein Teilbaum der Registry reserviert. Java-Programme bekommen einen Zweig, SOFTWARE/JavaSoft/Prefs unter HKEY_LOCAL_MACHINE beziehungsweise HKEY_CURRENT_USER zugewiesen. Es lässt sich nicht auf die gesamte Registry zugreifen!
- Unix und Mac OS X speichern die Einstellungen in XML-Dateien. Die Systemeigenschaften landen bei Unix unter /etc/.java/.systemPrefs und die Benutzereigenschaften lokal unter $HOME/.java/.userPrefs. Mac OS X speichert Benutzereinstellungen im Verzeichnis /Library/Preferences/.

Abbildung 11.8: UML-Diagramm Preferences
Preferences-Objekte lassen sich über statische Methoden auf zwei Arten erlangen:
- Die erste Möglichkeit nutzt einen absoluten Pfad zum Registry-Knoten. Die Methoden sind am Preferences-Objekt befestigt und heißen für die Benutzerumgebung userRoot() und für die Systemumgebung systemRoot().
- Die zweite Möglichkeit nutzt die Eigenschaft, dass automatisch jede Klasse in eine Paketstruktur eingebunden ist. userNodeForPackage(Class) oder systemNodeForPackage(Class) liefern ein Preferences-Objekt für eine Verzeichnisstruktur, in der die Klasse selbst liegt.
Beispiel |
Erfrage ein Benutzer-Preferences-Objekt über einen absoluten Pfad und über die Paketstruktur der eigenen Klasse: Preferences userPrefs = Preferences.userRoot().node( "/com/tutego/insel" ); |
abstract class java.util.prefs.Preferences
- static Preferences userRoot()
Liefert ein Preferences-Objekt für Einstellungen, die lokal für den Benutzer gelten. - static Preferences systemRoot()
Liefert ein Preferences-Objekt für Einstellungen, die global für alle Benutzer gelten.
11.9.2 Einträge einfügen, auslesen und löschen

Die Klasse Preferences hat große Ähnlichkeit mit den Klassen Properties beziehungsweise HashMap (vergleiche Kapitel 13, »Einführung in Datenstrukturen und Algorithmen«). Schlüssel/Werte-Paare lassen sich einfügen, löschen und erfragen. Allerdings ist die Klasse Preferences kein Mitglied der Collection-API, und es existiert auch keine Implementierung von Collection-Schnittstellen.
abstract class java.util.prefs.Preferences |
- abstract void put(String key, String value)
- abstract void putBoolean(String key, boolean value)
- abstract void putByteArray(String key, byte[] value)
- abstract void putDouble(String key, double value)
- abstract void putFloat(String key, float value)
- abstract void putInt(String key, int value)
- abstract void putLong(String key, long value)
Bildet eine Assoziation zwischen den Schlüsselnamen und dem Wert. Die Varianten mit den speziellen Datentypen nehmen intern eine einfache String-Umwandlung vor und sind nur kleine Hilfsmethoden; so steht in putDouble() nur put(key, Double.toString(value)). Die Hilfsmethode putByteArray() konvertiert die Daten nach der Base64-Kodierung und legt sie intern als String ab. - abstract String get(String key, String def)
- abstract boolean getBoolean(String key, boolean def)
- abstract byte[] getByteArray(String key, byte[] def)
- abstract double getDouble(String key, double def)
- abstract float getFloat(String key, float def)
- abstract int getInt(String key, int def)
- abstract long getLong(String key, long def)
Liefert den gespeicherten Wert typgerecht aus. Fehlerhafte Konvertierungen werden etwa mit einer NumberFormatException bestraft. Der zweite Parameter erlaubt die Angabe eines Alternativwerts, falls es keinen assoziierten Wert zu dem Schlüssel gibt. - abstract String[] keys()
Liefert alle Knoten unter der Wurzel, denen ein Wert zugewiesen wurde. Falls der Knoten keine Eigenschaften hat, liefert keys() ein leeres Feld. - abstract void flush()
Die Änderungen werden unverzüglich in den persistenten Speicher geschrieben.
Unser folgendes Programm richtet einen neuen Knoten unter /com/tutego/insel ein. Aus den über System.getProperties() ausgelesenen Systemeigenschaften sollen alle Eigenschaften, die mit »user.« beginnen, in die Registry übernommen werden:
Listing 11.16: com/tutego/insel/prefs/PropertiesInRegistry.java, Ausschnitt 1
static Preferences prefs = Preferences.userRoot().node( "/com/tutego/insel" );
static void fillRegistry()
{
for ( Object o : System.getProperties().keySet() )
{
String key = o.toString();
if ( key.startsWith("user.") && System.getProperty(key).length() != 0 )
prefs.put( key, System.getProperty(key) );
}
}
Um die Elemente auszulesen, kann ein bestimmtes Element mit getXXX() erfragt werden. Die Ausgabe aller Elemente unter einem Knoten gelingt am besten mit keys(). Das Auslesen kann eine BackingStoreException auslösen, falls der Zugriff auf den Knoten nicht möglich ist. Mit get() erfragen wir anschließend den mit dem Schlüssel assoziierten Wert. Wir geben »---« aus, falls der Schlüssel keinen assoziierten Wert besitzt:
Listing 11.17: com/tutego/insel/prefs/PropertiesInRegistry.java, Ausschnitt 2
static void display()
{
try
{
for ( String key : prefs.keys() )
System.out.println( key + ": " + prefs.get(key, "---") );
}
catch ( BackingStoreException e )
{
System.err.println( "Knoten können nicht ausgelesen werden: " + e );
}
}
Hinweis |
Die Größen der Schlüssel und Werte sind beschränkt! Der Knoten- und Schlüsselname darf maximal Preferences.MAX_NAME_LENGTH/MAX_KEY_LENGTH Zeichen umfassen, und die Werte dürfen nicht größer als MAX_VALUE_LENGTH sein. Die aktuelle Belegung der Konstanten gibt 80 Zeichen und 8 KiB (8.192 Zeichen) an. |
Um Einträge wieder loszuwerden, gibt es drei Methoden: clear(), remove() und removeNode(). Die Namen sprechen für sich.
11.9.3 Auslesen der Daten und Schreiben in einem anderen Format

Die Daten aus den Preferences lassen sich mit exportNode(OutputStream) beziehungsweise exportSubtree(OutputStream) im UTF-8-kodierten XML-Format in einen Ausgabestrom schreiben. exportNode(OutputStream) speichert nur einen Knoten, und exportSubtree(OutputStream) speichert den Knoten inklusive seiner Kinder. Und auch der umgekehrte Weg funktioniert: importPreferences(InputStream) importiert Teile in die Registrierung. Die Schreib- und Lesemethoden lösen eine IOException bei Fehlern aus, und eine InvalidPreferencesFormatException ist beim Lesen möglich, wenn die XML-Daten ein falsches Format haben.
11.9.4 Auf Ereignisse horchen

Änderungen an den Preferences lassen sich mit Listenern verfolgen. Zwei sind im Angebot:
- Der NodeChangeListener reagiert auf Einfüge- und Löschoperationen von Knoten.
- Der PreferenceChangeListener informiert bei Wertänderungen.
Es ist nicht gesagt, dass, wenn andere Applikationen die Einstellungen ändern, diese Änderungen vom Java-Programm auch erkannt werden.
Eine eigene Klasse NodePreferenceChangeListener soll die beiden Schnittstellen NodeChangeListener und PreferenceChangeListener implementieren und auf der Konsole die erkannten Änderungen ausgeben.
Listing 11.18: com/tutego/insel/prefs/ NodePreferenceChangeListener.java, NodePreferenceChangeListener
class NodePreferenceChangeListener implements
NodeChangeListener, PreferenceChangeListener
{
/* (non-Javadoc)
* @see java.util.prefs.NodeChangeListener#childAdded(java.util.prefs.NodeChangeEvent)
*/
@Override public void childAdded( NodeChangeEvent e )
{
Preferences parent = e.getParent(), child = e.getChild();
System.out.println( parent.name() + " hat neuen Knoten " + child.name() );
}
/* (non-Javadoc)
* @see java.util.prefs.NodeChangeListener#childRemoved
* (java.util.prefs.NodeChangeEvent)
*/
@Override public void childRemoved( NodeChangeEvent e )
{
Preferences parent = e.getParent(), child = e.getChild();
System.out.println( parent.name() + " verliert Knoten " + child.name() );
}
/* (non-Javadoc)
* @see java.util.prefs.PreferenceChangeListener#preferenceChange
* (java.util.prefs.PreferenceChangeEvent)
*/
@Override public void preferenceChange( PreferenceChangeEvent e )
{
String key = e.getKey(), value = e.getNewValue();
Preferences node = e.getNode();
System.out.println( node.name() + " hat neuen Wert " + value + " für " + key );
}
}
Zum Anmelden eines Listeners bietet Preferences zwei addXXXChangeListener()-Methoden:
Listing 11.19: com/tutego/insel/prefs/PropertiesInRegistry.java, addListener()
NodePreferenceChangeListener listener = new NodePreferenceChangeListener();
prefs.addNodeChangeListener( listener );
prefs.addPreferenceChangeListener( listener );
11.9.5 Zugriff auf die gesamte Windows-Registry

Wird Java unter MS Windows ausgeführt, so ergibt sich hin und wieder die Aufgabe, Eigenschaften der Windows-Umgebung zu kontrollieren. Viele Eigenschaften des Windows-Betriebssystems sind in der Registry versteckt, und Java bietet als plattformunabhängige Sprache keine Möglichkeit, diese Eigenschaften in der Registry auszulesen oder zu verändern. (Die Schnittstelle java.rmi.registry.Registry ist eine Zentrale für entfernte Aufrufe und hat mit der Windows-Registry nichts zu tun. Auch das Paket java.util.prefs mit der Klasse Preferences erlaubt nur Modifikationen an einem ausgewählten Teil der Windows-Registry.)
Um von Java aus auf alle Teile der Windows-Registry zuzugreifen, gibt es mehrere Möglichkeiten, unter anderem:
- Um auf allen Werten der Windows-Registry, die dem Benutzer zugänglich sind, operieren zu können, lässt sich mit einem Trick ab Java 1.4 eine Klasse nutzen, die Preferences unter Windows realisiert: java.util.prefs.WindowsPreferences. Damit ist keine zusätzliche native Implementierung – und damit eine Windows-DLL im Klassenpfad – nötig. Die Bibliothek https://sourceforge.net/projects/jregistrykey/ realisiert eine solche Lösung.
- eine native Bibliothek, wie das Windows Registry API Native Interface (http://tutego.com/go/jnireg), die frei zu benutzen ist und unter keiner besonderen Lizenz steht
- das Aufrufen des Konsolenregistrierungsprogramms reg zum Setzen und Abfragen von Schlüsselwerten
Registry-Zugriff selbst gebaut
Für einfache Anfragen lässt sich der Registry-Zugriff schnell auch von Hand erledigen. Dazu rufen wir einfach das Kommandozeilenprogramm reg auf, um etwa den Dateinamen für den Desktop-Hintergrund anzuzeigen:
$ reg query "HKEY_CURRENT_USER\Control Panel\Desktop" /v Wallpaper
! REG.EXE VERSION 3.0
HKEY_CURRENT_USER\Control Panel\Desktop
Wallpaper REG_SZ C:\Dokumente und Einstellungen\tutego\Anwendungsdaten\Hg.bmp
Wenn wir reg von Java aufrufen, haben wir den gleichen Effekt:
Listing 11.20: com/tutego/insel/lang/JavaWinReg.java, main()
ProcessBuilder builder = new ProcessBuilder(
"reg", "query",
"\"HKEY_CURRENT_USER\\Control Panel\\Desktop\"", "/v", "Wallpaper" );
Process p = builder.start();
Scanner scanner = new Scanner( p.getInputStream() )
.useDelimiter( " \\w+\\s+\\w+\\s+" );
scanner.next();
System.out.println( scanner.next() );
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.