/******************************************************************************* * Copyright (c) 2005-2011, G. Weirich and Elexis * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * G. Weirich - initial implementation * *******************************************************************************/ package ch.rgw.io; import java.awt.Rectangle; import java.io.Serializable; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.rgw.tools.ExHandler; import ch.rgw.tools.StringTool; import ch.rgw.tools.TimeTool; /** * Abstrakte Basisklasse für die Speicherung applikationsspezifischer Daten.<br> * Änderungen der Settings sind immer volatil. Erst mit dem Auruf von flush() erfolgt eine * persistierende Speicherung (deren genaues Ziel Sache der konkreten Implementation ist. mit undo() * kann jeweils der Stand nach dem letzten flush() wiederhergestellt werden. Es erfolgt euch keine * implizite Speicherung beim Programmende! Alle Änderungen, die nicht explizit mit flush() * gesichert werden, sind verloren. */ public abstract class Settings implements Serializable, Cloneable { public static String Version(){ return "4.2.2"; } private static int SerializedVersion = 5; private static final long serialVersionUID = 0xdcb17fe20021006L + SerializedVersion; protected static Logger log = null; protected Hashtable node; private volatile String path = null; private volatile boolean dirty = false; // protected String name; static { if (log == null) log = LoggerFactory.getLogger("Settings"); //$NON-NLS-1$ } protected Settings(){ node = new Hashtable(); dirty = false; } protected Settings(byte[] flat){ node = StringTool.fold(flat, StringTool.NONE, null); dirty = true; } protected Settings(Hashtable n){ node = (n == null) ? new Hashtable() : n; dirty = true; } protected void cleaned(){ dirty = false; } public boolean isDirty(){ return dirty; } public String toString(){ return StringTool.enPrintable(StringTool.flatten(node, StringTool.NONE, null)); } protected String getPath(){ if (path == null) { return ""; } return path; } /* * protected void finalize() { flush(); } */ public void clear(){ node.clear(); dirty = true; } public double get(String key, double defvalue){ String res = get(key, null); if (res == null) { return defvalue; } try { return Double.parseDouble(res); } catch (Exception ex) { ExHandler.handle(ex); log.error("Parse fehler für Double " + res); return defvalue; } } public String get(String key, String defvalue){ Hashtable subnode = findParent(key, false); if (subnode == null) { return defvalue; } Object v = subnode.get(getLeaf(key)); return (StringTool.isNothing(v)) ? defvalue : (String) v; } @SuppressWarnings("unchecked") public boolean set(String key, String value){ if ((key == null) || (value == null)) { return false; } Hashtable subnode = findParent(key, true); dirty = true; return (subnode.put(getLeaf(key), value) != null); } private String getLeaf(String key){ int id = key.lastIndexOf('/'); if (id != -1) { String leaf = key.substring(id + 1); return leaf; } return key; } @SuppressWarnings("unchecked") private Hashtable findParent(String key, boolean CreateIfNeeded){ String[] path1 = key.split("/"); Hashtable subnode = node; for (int i = 0; i < path1.length - 1; i++) { Object v = subnode.get(path1[i]); if ((v == null) || (!(v instanceof Hashtable))) { if (CreateIfNeeded == true) { v = new Hashtable(); subnode.put(path1[i], v); dirty = true; } else { return null; } } subnode = (Hashtable) v; } return subnode; } @SuppressWarnings("unchecked") public String[] keys(String nod){ Settings sn = getBranch(nod, false); if (sn == null) { return null; } ArrayList al = sn.keys(); return (String[]) al.toArray(new String[0]); } @SuppressWarnings("unchecked") public String[] nodes(String nod){ Settings sn = getBranch(nod, false); if (sn == null) { return null; } ArrayList al = sn.nodes(); return (String[]) al.toArray(new String[0]); } @SuppressWarnings("unchecked") public ArrayList keys(){ Enumeration en = node.keys(); ArrayList dest = new ArrayList(); while (en.hasMoreElements()) { Object k = en.nextElement(); if (node.get(k) instanceof Hashtable) { continue; } dest.add(k); } return dest; } @SuppressWarnings("unchecked") public ArrayList nodes(){ Enumeration en = node.keys(); ArrayList dest = new ArrayList(); while (en.hasMoreElements()) { Object k = en.nextElement(); if (node.get(k) instanceof Hashtable) { dest.add(k); } } return dest; } /** * Einen Zweig dieser Settings holen oder erstellen.<br> * Die Implementation ist Sache der Unterklasse. In der Registry kann ein Zweig direkt auf einem * Zweig abgebildet werden, ein XML-File kann einen entsprechenden Node erstellen, ein Flatfile * wird einfach Einträge des Typs präfix/eintrag erstellen. * * @param name * Der Name des Zweigs * @param CreateIfNotExist * Der Zweig wird erstellt, wenn er noch nicht existiert * @return ein neues Settings-Object, das den Zweig repräsentiert */ @SuppressWarnings("unchecked") public Settings getBranch(String name, boolean CreateIfNotExist){ Hashtable parent = findParent(name, CreateIfNotExist); if (parent == null) { return null; } String id = getLeaf(name); Object k = parent.get(id); if ((k == null) || (!(k instanceof Hashtable))) { if (CreateIfNotExist) { k = new Hashtable(); parent.put(id, k); dirty = true; } else { return null; } } try { Settings n = (Settings) this.clone(); if (path == null) { n.path = name + "/"; } else { n.path = path + name + "/"; } n.node = (Hashtable) k; return n; } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block ExHandler.handle(e); return null; } } public ArrayList<String> getAll(){ ArrayList<String> ret = new ArrayList<String>(); addNode(ret, "", node); return ret; } public Iterator<String> iterator(){ ArrayList<String> al = getAll(); Iterator<String> it = al.iterator(); return it; } @SuppressWarnings("unchecked") private void addNode(ArrayList dest, String name, Hashtable n){ Enumeration en = n.keys(); while (en.hasMoreElements()) { String k = (String) en.nextElement(); Object o = n.get(k); if (o instanceof Hashtable) { addNode(dest, name + k + "/", (Hashtable) o); } else { dest.add(name + k); } } } /** * Einen Fingerprint über alle Einträge erstellen.<br> * * @param ex * Eintrag, welcher nicht einbezogen wird * @return den hashcode * @todo Dies sollte auf einen MD5 oder SHA-hash umgstellt werden */ private long getHashCode(String ex){ long hc = 0; ArrayList<String> keys = getAll(); for (int i = 0; i < keys.size(); i++) { String a = (String) keys.get(i); if (a.equals(ex)) { continue; } String b = get(a, ""); hc += b.hashCode(); hc <<= 2; if (hc < 0) hc |= 1; } return hc; } public long createHashCode(String ex){ long ret = getHashCode(ex); set(ex, Long.toString(ret)); return ret; } /** * Den mit createHashCode erstellten Fingerprint überprüfen. * * @param ex * Eintrag, der den hashcode enthült * @return true bei übereinstimmung */ public boolean checkHashCode(String ex){ long oldval = Long.parseLong(get(ex, "-1")); long nval = getHashCode(ex); return (nval == oldval); } /** * Einen Integer-Wert setzen. * * @param key * Schlüssel * @param value * Wert */ public void set(String key, int value){ set(key, Integer.toString(value)); } /** * Ein Rechteck eintragen. * * @param key * Schlüssel * @param rec * Wert */ public void set(String key, Rectangle rec){ if (rec == null) return; String v = Integer.toString(rec.x) + "," + Integer.toString(rec.y) + "," + Integer.toString(rec.width) + "," + Integer.toString(rec.height); set(key, v); } /** * Einen Datum/Zeitwert eintragen. * * @param key * Schlüssel * @param d * Datum/Zeit als ch.rgw.tools.timeTool */ public void set(String key, TimeTool d){ set(key, d.toString(TimeTool.FULL_MYSQL)); } /** * Einen Schlüssel entfernen. * * @param key * der Schlüssel */ public void remove(String key){ Hashtable p = findParent(key, false); if (p != null) { p.remove(getLeaf(key)); dirty = true; } } /** * Einen Integerwert auslesen. * * @param key * Schlüssel * @param defvalue * Defaultwert, falls der Schlüssel nicht existiert * @return der Wert resp. der Defaultwet */ public int get(String key, int defvalue){ String v = get(key, Integer.toString(defvalue)); try { return Integer.parseInt(v); } catch (Exception ex) { ExHandler.handle(ex); log.debug("Int parse Fehler. Gebe Default zurück (" + defvalue + ")"); set(key, defvalue); return defvalue; } } /** * Einen String auslesen, dabei alle \ nach / wandeln. * * @param key * Schlüssel * @param defvalue * Defaultwert, falls der Schlüssel nicht existiert * @return Wert */ public String getQuoted(String key, String defvalue){ String vorl = get(key, defvalue); return vorl.replaceAll("\\\\", "/"); } public String[] getStringArray(String key){ String raw = get(key, null); if (StringTool.isNothing(raw)) { return null; } return raw.split(","); } /** * Einen Datum/Zeitwert auslesen. * * @param key * Schlüssel * @return ein ch.rgw.timeTool oder null, wenn der Schlüssel nicht existiert oder ein ungültiges * Format hat. */ public TimeTool getDate(String key){ String d = get(key, ""); if (d.equals("")) return null; try { return new TimeTool(d); } catch (Exception ex) { ExHandler.handle(ex); return null; } } /** * Einen rechteck-Wert auslesen. * * @param key * Schlüssel * @return das Rectangle oder null, wenn der Schlüssel nicht existiert oder der Eintrag ein * ungültiges Format hat. */ public Rectangle get(String key){ String v = get(key, ""); if (v == null) return null; String[] r = v.split(","); if (r.length != 4) return null; return new Rectangle(Integer.parseInt(r[0]), Integer.parseInt(r[1]), Integer.parseInt(r[2]), Integer.parseInt(r[3])); } public boolean get(String key, boolean defvalue){ String v = get(key, null); if (v == null) { return defvalue; } if (v.equals("1")) { return true; } if (v.equals("true")) { return true; } return false; } public void set(String key, boolean value){ if (value == true) { set(key, "1"); } else { set(key, "0"); } } /** * Alle Änderungen sichern. Bei Programmabbruch ohne flush werden alle Änderungen seit dem * letzten flush() resp. Programmstart verworfen * */ public void flush(){ if (dirty == true) { flush_absolute(); dirty = false; } } protected abstract void flush_absolute(); /** * Alle Änderungen seit dem letzten flush() bzw. Programmstart verwerfen. * */ public abstract void undo(); /** * Ein anderes Settings-Objekt einfügen */ public static final int OVL_REPLACE = 1; // Alles löschen und aus other holen public static final int OVL_REPLACE_EXISTING = 2; // Existierende mit other überlagern public static final int OVL_ADD_MISSING = 4; // Nur fehlende aus other nehmen public static final int OVL_ALL = 6; // Alle Existierenden und neuen public void overlay(Settings other, int mode){ ArrayList<String> otherEntries = other.getAll(); if ((mode & OVL_REPLACE) != 0) { node.clear(); } for (int i = 0; i < otherEntries.size(); i++) { String el = (String) otherEntries.get(i); if (get(el, null) != null) { if ((mode & OVL_REPLACE_EXISTING) != 0) { set(el, other.get(el, null)); } } else { if ((mode & (OVL_ADD_MISSING | OVL_REPLACE)) != 0) { set(el, other.get(el, null)); } } } } }