11.7 Die Properties-Klasse
Unter älteren Windows-Versionen gibt es INI-Dateien, in denen ebenfalls Datenpaare aus Schlüssel und Wert abgelegt sind. Für eine alte Windows-Umgebung ist beispielsweise win.ini eine wichtige Datei, in der Systemeinstellungen gesichert sind.
Beispiel Ein Ausschnitt aus der Datei win.ini
[Desktop]
Wallpaper=(None)
TileWallpaper=1
WallpaperStyle=0
Pattern=(None)
...
[colors]
Scrollbar=224 224 224
Background=0 128 128
ActiveTitle=0 0 128
|
Es finden sich in der Datei mit einem eindeutigen Schlüsselwort verbundene Zeichenketten. Die INI-Datei kann einfach mit einem beliebigen ASCII-Editor angepasst werden und ist somit leicht für Änderungen zugänglich. In Java gibt es einen ähnlichen Mechanismus. Die Klasse Properties erlaubt, solche Wertepaare aufzubauen und dadurch die Daten aus dem Programmtext herauszuziehen und in eine Art INI-Datei zu legen, die eine Änderung der Werte bequem ohne Neucompilierung möglich macht. Leider kann eine Eigenschaften-Datei nicht segmentiert werden. Hierarchisch benannte Eigenschaften wie Desktop. Wallpaper sind eine interessante Alternative, doch tauchen sie dann quer gewürfelt in der Datei auf, da ein Assoziativspeicher keine Sortierung besitzt.
11.7.1 Über die Klasse Properties
Die Properties-Klasse ist eine Erweiterung von Hashtable, da die Speicherung der Einstellungsdaten in dieser Datenstruktur erfolgt. Ein Properties-Objekt erweitert die Hash-Tabelle um die Möglichkeit, die Schlüssel-Werte-Paare in einem festgelegten Format aus einer Textdatei beziehungsweise einem Datenstrom zu laden und wieder zu speichern. Außerdem kommt noch eine Hierarchie von Hash-Tabellen hinzu; jedes Properties-Exemplar hat ein Eltern-Properties-Objekt, das gegebenenfalls automatisch durchsucht wird. Und gerade weil Hashtable erweitert wird, sind auch alle Methoden der Klasse Hashtable auf ein Properties-Objekt anwendbar. Das macht nicht für alle Methoden Sinn und ist auch nicht in jedem Fall problemlos. Dass Properties eine Unterklasse von Hashtable ist, ist ähnlich fragwürdig wie die Vererbungsbeziehung Stack/Vector.
Beispiel Das nachfolgende Programm erzeugt zwei Properties-Objekte. Eine davon ist eine Standardliste und die andere soll eine benutzerdefinierte sein, die anfänglich alle Einstellungen von der Standardliste übernimmt. Dieses Übernehmen ist durch einen speziellen Konstruktor möglich, denn beim Erzeugen der benutzerdefinierten Liste wird eine andere Liste einfach im Konstruktor übergeben.
|
Listing 11.7 TestProperties.java
import java.util.Properties;
class TestProperties
{
public static void main( String args[] )
{
Properties defaultProperties = new Properties(),
userProperties = new Properties(defaultProperties);
defaultProperties.setProperty( "User", "Standard" );
defaultProperties.setProperty( "Version", "" + 0.02f );
userProperties.setProperty( "User", "Ulli Schnulli" );
userProperties.setProperty( "MagCleverUndSmart", "" + true );
System.out.println( "Default Properties:" );
defaultProperties.list( System.out );
System.out.println( "\nUser Properties:" );
userProperties.list( System.out );
System.out.println( "Property 'User' is '" +
userProperties.getProperty("User") + "'" );
}
}
Testen wir das Programm, so fällt ein Zusammenhang besonders auf: Obwohl wir zunächst das Default- und User-Objekt erzeugen, werden die später zum Default-Objekt hinzugefügten Daten an das User-Objekt weitergereicht. Nachträglich dem Default-Objekt zugeordnete Werte kommen also auch zum Aufrufer zurück. Die Implementierung zeigt: Zuerst durchsucht ein Property-Exemplar die eigene, in ihm enthaltene Hash-Tabelle. Liefert diese keinen Eintrag oder keinen Wert vom Typ String, so wird das im Konstruktoraufruf angegebene Property-Objekt durchsucht. Auf diese Weise lassen sich mehrstufige Hierarchien von Property-Verzeichnissen konstruieren. Damit ergibt sich die Ausgabe:
Default Properties:
-- listing properties --
Version=0.02
User=Standard
User Properties:
-- listing properties --
MagCleverUndSmart=true
Version=0.02
User=Ulli Schnulli
Property 'User' is 'Ulli Schnulli'
11.7.2 put(), get() und getProperties()
Ein weiterer Augenmerk ist auf die put()-Funktion zu legen. Sie gibt es in der Klasse Properties nicht, denn put() wird von Hashtable geerbt. Wir sollten sie jedoch nicht verwenden, da es über sie möglich ist, Objekte einzufügen, die nicht vom Typ String sind. Properties von Hashtable abzuleiten ist übrigens ein genauso fragwürdiges Beispiel für den Einsatz von Vererbung (wie Stack als Unterklasse von Vector).
Das gleiche Argument könnte für get() gelten, doch zwei Dinge sprechen dagegen: Zunächst einmal, dass wir beim get() aus einem Hashtable-Objekt immer ein Object-Objekt bekommen und daher meistens einen Cast brauchen, und zweitens durchsucht diese Methode lediglich den Inhalt des angesprochenen Properties-Exemplars. getProperties() arbeitet da etwas anders. Nicht nur, dass der Rückgabewert automatisch ein String ist, sondern auch, dass nachgeschachtelte Properties-Objekte mit durchsucht werden, die zum Beispiel Standardwerte speichern.
class java.util.Properties
extends Hashtable
|
|
Properties()
Erzeugt ein leeres Properties-Objekt ohne Schlüssel und Werte. |
|
Properties( Properties defaults )
Erzeugt ein leeres Properties-Objekt, das bei Anfragen auch auf die Einträge in dem übergebenen Properties-Objekt zurückgreift. |
|
String getProperty( String key )
Sucht in den Properties nach der Zeichenkette key als Schlüssel und liefert den zugehörigen Wert. Durchsucht auch nachgeschaltete Properties-Objekte. |
|
String getProperty( String key, String default )
Sucht in den Properties nach der Zeichenkette key als Schlüssel und liefert den zugehörigen Wert. Ist der Schlüssel nicht vorhanden, wird der String default zurückgegeben. |
|
Object setProperty( String key, String value )
Trägt Schlüssel und Wert im Properties-Exemplar ein. Existiert der Schlüssel schon, wird er überschrieben. Mitunter verdeckt der Schlüssel den Wert der Property in der übergeordneten Property. |
11.7.3 Eigenschaften ausgeben
Eine kleine Hilfe zum Testen bietet die Bibliothek durch eine list()-Methode. Sie wandert einfach durch die Daten eines Properties-Exemplars und gibt sie auf einem PrintStream oder PrintWriter aus. Das sind Datenströme, denen wir uns näher im Eingabe- und Ausgabekapitel widmen wollen. Eine Ausgabe auf dem Bildschirm erhalten wir mit list(System.out).
Die Paare folgen einer Kopfzeile
-- listing properties --
und haben die Form:
Schlüssel=Wert
Umfasst der Wert mehr als 40 Zeichen, wird die Ausgabe mit »...« abgekürzt.
class java.util.Properties
extends Hashtable
|
|
void list( PrintStream out )
Listet die Properties auf dem PrintStream aus. |
|
void list( PrintWriter out )
Listet die Propierties auf dem PrintWriter aus. |
11.7.4 Systemeigenschaften der Java-Umgebung
In einem weiteren Beispiel erzeugen wir ein Properties-Objekt, welches direkt die System-Java-Properties widerspiegelt. Nach dem Hinzufügen eines Eintrags schreiben wir diese Werte in eine Datei, lesen sie wieder und geben sie mit der list()-Methode aus. Eine umfangreiche Liste von Systemeigenschaften bietet die Web-Seite http://www.tolstoy.com/samizdat/sysprops.html.
Listing 11.8 SaveProperties.java
import java.io.*;
import java.util.*;
class SaveProperties
{
public static void main( String args[] )
{
String filename = "properties.txt";
try
{
FileOutputStream propOutFile =
new FileOutputStream( filename );
Properties p1 = new Properties( System.getProperties() );
p1.setProperty( "MeinNameIst", "Forest Gump" );
p1.store( propOutFile, "Eine Insel mit zwei Bergen" );
FileInputStream propInFile = new FileInputStream( filename );
Properties p2 = new Properties();
p2.load( propInFile );
p2.list( System.out );
}
catch ( FileNotFoundException e ) {
System.err.println( "Can't find " + filename );
}
catch ( IOException e ) {
System.err.println( "I/O failed." );
}
}
}
class java.util.Properties
extends Hashtable
|
|
void load( InputStream inStream )
Lädt eine Properties-Liste aus einem Eingabestrom. |
|
void store( OutputStream out, String header )
Speichert die Properties-Liste mit Hilfe des Ausgabestroms ab. Am Kopf der Datei wird eine Kennung geschrieben, die im zweiten Argument steht. Die Kennung darf auch null sein. |
|
void Enumeration propertyNames()
Liefert eine Enumeration von allen Schlüsseln in der Property-Liste inklusive der Standardwerte aus nachgeschalteten Properties. |
11.7.5 Browser-Version abfragen
Eine besondere Eigenschaft bezeichnet die Zeichenkette browser.version. Ob es sich bei dem Browser um einen Internet Explorer oder um einen Netscape Communicator handelt, können wir aus der Variablen java.vendor auslesen.
String prop = System.getProperty( "java.vendor" );
boolean ie = prop.indexOf( "Microsoft Corp." ) >= 0;
Bei der Anweisung System.getProperty(key) ist das Properties-Objekt gut versteckt. In einer etwas längeren Schreibweise ist das etwa:
Properties prop = System.getProperties();
String s = prop.getProperty( "property" );
11.7.6 Properties von der Konsole aus setzen
Eigenschaften lassen sich auch bei Programmstart von der Konsole aus setzen. Dies ist praktisch für eine Eigenschaft, die beispielsweise das Verhalten des Programms steuert oder das Programm konfiguriert. In der Kommandozeile werden mit »-D« der Name der Eigenschaft und ihr Wert angegeben. Viele Entwicklungsumgebungen erlauben es, diese in einem Fenster zu setzen. Die Informationen tauchen nicht bei der Argument-Liste in der main()-Methode auf, da sie vor dem Namen der Klasse stehen und bereits von der Java-Laufzeitumgebung verarbeitet werden.
Um die Eigenschaften auszulesen, gibt es zwei Möglichkeiten. Eine davon ist überraschend.
Listing 11.9 SetProperty.java
class SetProperty
{
static public void main( String args[] )
{
boolean debug = false;
String prop = System.getProperty( "DEBUG" );
// Erster Weg
if ( prop != null )
debug = Boolean.valueOf(prop).booleanValue();
if ( debug )
System.out.println( "Wir dürfen debuggen" );
// Zweiter Weg
System.out.println( Boolean.getBoolean("DEBUG") );
}
}
Auf der Konsole folgt die Ausgabe
$ java -DDEBUG=true SetProperty
Wir dürfen debuggen
true
Wir bekommen über getProperty() einen String zurück, der den Wert anzeigt. Falls es überhaupt keine Eigenschaft mit diesem Namen gibt, erhalten wir stattdessen null. So wissen wir auch, ob dieser Wert überhaupt gesetzt wurde. Im Fall von Wahrheitswerten gibt es die Sonderfunktion getBoolean() in der Klasse Boolean, die aus der Eigenschaften-Liste der Klasse System eine Eigenschaft mit dem angegebenen Namen heraussucht. Es erstaunt, diese Funktion in der Wrapper-Klasse Boolean anzutreffen. Der einzige Vorteil gegenüber der Lösung mit der valueOf()-Methode ist, dass die Groß-/Kleinschreibung beim Wert keine Rolle spielt. Dies erkaufen wir mit dem Nachteil, dass wir bei einem false nicht unterscheiden können, ob es die Eigenschaft nicht gibt, oder ob sie mit dem Wert false belegt ist.
11.7.7 Windows-typische INI-Dateien
Für die INI-Dateien mit Segmenten gibt es in Java keine Unterstützung, doch die Entwicklergemeinde hat die Lücke schnell gefüllt. So lässt sich von Aresch Yavari unter http://www.geocities.com/Area51/9851/INIFiles.html eine Unterstützung für die Windows-INI-Files beziehen.
|