Dienstag, 21. Dezember 2010

Anwendungseigenschaften speichern

Eigentlich gibt es ja kaum eine Anwendung, bei der man nicht ein paar Einstellungs- oder Konfigurationsmöglichkeiten haben möchte. Die Out-of-the-Box-Lösung unter Java ist hier die java.util.Properties Klasse, mit der Eigenschaften gelesen und geschrieben werden können. Aussehen könnte das ganze wie folgt:

    /**
     * Lese properties file.
     */
    public void readProperties() {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("filename.properties"));
        } catch (IOException e) { 
            e.printStackTrace();
        }
    }

    /**
     * Schreibe properties file.
     */
    public void writeProperties(Properties properties) {
        try {
            properties.store(new FileOutputStream("filename.properties"), null);
        } catch (IOException e) { 
            e.printStackTrace(); 
        }
    }

Der Zugriff erfolgt dann, gleich einer HashMap, via props.getProperty("key"). Das ist alles sehr einfach und reicht oftmals aus, um ein paar Werte zur Laufzeit wegzuschreiben. Will man aber Arrays, Objekte oder ganze Wertebeziehungen speichern, muss man diese zunächst in einen String codieren bzw. beim Lesen wieder decodieren. Ist man im Begriff mit so etwas zu beginnen, empfiehlt es sich gleich etwas anderes zu verwenden, weil hier die Rattenschwanzgefahr recht groß ist und letztendlich nur vom Projektziel ablenkt. Gesehen habe ich das allerdings schon mehrfach. Dabei bringt Java für anspruchsvollere Anwendungseigenschaften noch etwas anderes, besseres mit.

Im Package java.util.prefs gibt es die Klasse Preferences, die eine hierarchische Werte- und Objektablage bietet. So ist mittels Preferences.userRoot() eine Rootknoten erhältlich, in den wir bereits verschiedene Objekte ablegen können. Besser ist es aber, einen eigenen Knoten für die Anwendung bereitzustellen, um nicht mit anderen Anwendungen zu kollidieren. Preferences werden nämlich im Benutzerverzeichnis für alle Anwendungen unter .userPrefs/prefs.xml abgelegt. Via Preferences.systemRoot() lassen sich auch systemweite Einstellungen speichern. Um die Anwendungseinstellungen zu laden empfiehlt es sich, einen Unterknoten zu verwenden, der wie folgt erzeugt/geladen werden kann: Preferences.userRoot().node("TestApp"). Dieser Knoten kann nun als Basis für weitere Unterknoten oder für die Werteablage dienen. Um das Laden und Speichern müssen wir uns bequemer Weise nicht kümmern. Unser Knoten befindet sich nach dem Neustarten der Anwendung im gleichen Zustand. Der Zugriff auf die Knoteneigenschaften erfolgt mittels put- und get-Methoden.

java.util.prefs.Preferences appNode = Preferences.userRoot().node("TestApp");
appNode.putInt("fontsize", 10); //Eigenschaft setzen
int fontsize = appNode.getInt("fontsize", 10); //Eigenschaft lesen

Das ganze kann noch weiter getrieben werden, so dass zum Beispiel für jedes Paket ein neuer Knoten verwendet wird.

Preferences packageNode = appNode.node(Test.class.getName())

Bis jetzt lassen sich aber nur die gängigen Wrapper-Klassen sowie Strings und Bytearrays ablegen. Um (fast) beliebige Objekte abzulegen, lässt sich die Serialisierbarkeit von Objektinstanzen nutzen. Die zugrundeliegende Klasse muss also das Serializable-Interface implementieren. Die der Klasse zugrunde liegenden Objektinstanzen können anschließend in ein Bytearray umgewandelt werden, das wiederum mit der Methode putByteArray in den Preferences-Knoten gespeichert werden kann.

   /**
     * Objektinstanzen serialisieren.
     */
    static private byte[] object2Bytes(Object o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        return baos.toByteArray();
    }

Der Rückweg, aus dem Bytearray eine Objektinstanz zu erzeugen, ist ebenso einfach.

   /**
     * Serialisierte Objektinstanz wiederherstellen
     */
    static private Object bytes2Object(byte raw[]) throws IOException,
            ClassNotFoundException {
        ByteArrayInputStream bais = new ByteArrayInputStream(raw);
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = ois.readObject();
        return o;
    }

Bei der Objektserialisierung ist immer darauf zu achten, dass die Member-Variablen der zugrundeliegenden Klasse wiederum serialisierbare Klassen referenzieren. Andernfalls sind die Member-Variablen mit transient zu kennzeichnen. Das Gilt zum Beispiel für Streams, die nicht einfach serialisiert werden können. Bei der Wiederherstellung enthalten diese Member-Variablen den Wert null.

Die Datenablage erfolgt - wie nicht anders zu erwarten - im XML-Format. Das Endergebnis  können wir uns auch ausgeben lassen.

public static void printPreferences(Preferences prefs) throws IOException,
            BackingStoreException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        prefs.exportNode(byteArrayOutputStream);
        System.out.println(new String(byteArrayOutputStream.toByteArray()));
    }

Und die Ausgabe.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
<preferences EXTERNAL_XML_VERSION="1.0">
  <root type="user">
    <map/>
    <node name="TestApp">
      <map>
        <entry key="fontsize" value="10"/>
      </map>
    </node>
  </root>
</preferences> 
 
Links: IBM Developerworks 

Keine Kommentare:

Kommentar veröffentlichen