/* * This file is part of jHaushalt. * jHaushalt is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * jHaushalt is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with jHaushalt; if not, see <http://www.gnu.org/licenses/>. * (C)opyright 2002-2010 Dr. Lars H. Hahn */ package haushalt.daten; import haushalt.daten.zeitraum.AbstractZeitraum; import haushalt.gui.TextResource; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.StringTokenizer; import java.util.logging.Logger; /** * Die Datenbasis beinhaltet alle Buchungen und dient zum * Zugriff auf alle Daten. Sie verwaltet die Register und * Kategorien. In der Datenbasis sind auch die gemerkten * Buchungen gespeichert. * * @author Dr. Lars H. Hahn * @version 2.5.3/2008.03.31 */ /* * 2008.03.31 BugFix: Beim Laden der Register richtige Reihenfolge beachten * 2008.02.06 Verbesserter Test auf geaenderte Daten; * Internationalisierung * 2008.02.04 Umstellung der Intervalle bei den autom. Buchungen auf Index * 2008.01.15 BugFix: Falls der Name des Intervall bei der automatischen Buchung * unbekannt ist, wird jetzt "Jahr" angenommen * 2007.05.24 Ausführen von automatischen Buchungen bis zu einem Datum * 2007.03.28 BugFix: getKategorieSalden summierte, wenn die Unterkategorien * NICHT verwendet werden, falsch auf (Lösung durch Nils op den Winkel) * 2007.02.22 Erweiterung: Ausgabe aller Buchungen * 2007.02.14 Verschieben eines Registers in der Register-Liste um ein * Offset hinzugefügt. * 2007.02.13 Automatisches Einsortieren eines Registers entfernt * 2007.02.12 Umbenennen von Registern hinzugefügt * 2006.06.16 Erweiterung um automatische Umbuchung * 2006.01.27 Die Entscheidung, ob Unterkategorien verwendet * werden fällt nicht mehr hier global, sondern * individuell in den Auswertungen * 2005.03.10 Erweiterung: Gemerkte Buchungen ab Datum * 2005.02.18 Erweiterung: Ersetzen einer Buchung liefert Position zurück. * 2004.08.25 BugFix: Einfügen einer StandardBuchung liefert Position zurück. */ public class Datenbasis { public static final String VERSION_DATENBASIS = "2.1.2"; private static int cacheHit = 0; private static int cacheMiss = 0; private static final boolean DEBUG = false; private static final TextResource RES = TextResource.get(); private static final Logger LOGGER = Logger.getLogger(Datenbasis.class.getName()); private static final String[] LEGACY_INTERVALL_NAMEN = {"Woche", "Monat", "Quartal", "Halbjahr", "Jahr"}; private boolean geaendert = false; // -- Kategorien -------------------------------------------- private final ArrayList<EinzelKategorie> kategorieListe; // -- Register ---------------------------------------------- private final ArrayList<Register> registerListe = new ArrayList<Register>(); // -- Suchen und Ersetzen ----------------------------------- private int registerSuchIdx = 0; private int buchungSuchIdx = 0; private AbstractZeitraum zeitraumImCache; private String registerImCache; private boolean cacheAktuell = false; private boolean cacheMitUnterkategorien = true; // -- gemerkte Buchungen ------------------------------------ private Datum startDatumGemerkteBuchungen = new Datum(); private final ArrayList<AbstractBuchung> gemerkteBuchungen = new ArrayList<AbstractBuchung>(); private final ArrayList<String> gemerkteBuchungenText = new ArrayList<String>(); // -- Auto-Buchung ------------------------------------------ private final ArrayList<StandardBuchung> autoStandardBuchungen = new ArrayList<StandardBuchung>(); private final ArrayList<Register> autoStandardBuchungRegister = new ArrayList<Register>(); private final ArrayList<Integer> autoStandardBuchungIntervalle = new ArrayList<Integer>(); private final ArrayList<Umbuchung> autoUmbuchungen = new ArrayList<Umbuchung>(); private final ArrayList<UmbuchungKategorie> autoUmbuchungRegister = new ArrayList<UmbuchungKategorie>(); private final ArrayList<Integer> autoUmbuchungIntervalle = new ArrayList<Integer>(); private String filename; public Datenbasis() { this.kategorieListe = new ArrayList<EinzelKategorie>(); this.kategorieListe.add(EinzelKategorie.SONSTIGES); } public static boolean givenVersionEqualsDatabaseVersion(final String version) { return version.equals("jHaushalt" + VERSION_DATENBASIS); } /** * Ist <code>true</code>, wenn Daten geändert wurden. * * @return geändert oder nicht geändert */ public boolean isGeaendert() { return this.geaendert; } /** * Wird aufgerufen, wenn die Daten geaendert wurden */ public void setGeaendert() { this.geaendert = true; } /** * Liefert die IKategorie mit dem angegebene Namen zurück. * Wenn sie noch nicht existiert, wird sie erzeugt. * * @param name * Name der IKategorie * @param hauptkategorie * Hauptkategorie * @return gesuchte IKategorie */ public EinzelKategorie findeOderErzeugeKategorie(final String name, final EinzelKategorie hauptkategorie) { final EinzelKategorie kategorie = new EinzelKategorie(name, hauptkategorie); final int pos = Collections.binarySearch(this.kategorieListe, kategorie); if (pos < 0) { this.kategorieListe.add(-pos - 1, kategorie); this.geaendert = true; return kategorie; } return this.kategorieListe.get(pos); } public EinzelKategorie findeOderErzeugeKategorie(final String vollerName) { final int n = vollerName.indexOf(":"); if (n == -1) { return findeOderErzeugeKategorie(vollerName, null); } final EinzelKategorie hauptkategorie = findeOderErzeugeKategorie(vollerName.substring(0, n), null); return findeOderErzeugeKategorie(vollerName.substring(n + 1), hauptkategorie); } public boolean isKategorie(final String name, final EinzelKategorie hauptkategorie) { final EinzelKategorie kategorie = new EinzelKategorie(name, hauptkategorie); final int pos = Collections.binarySearch(this.kategorieListe, kategorie); if (pos < 0) { return false; } return true; } /** * Liefert ein Array mit allen Kategorien. * Wenn die Unterkategorien verwendet werden sollen, * werden alle Kategorien geliefert, sonst nur die * Haupt-Kategorien * * @return Array mit den Kategorien */ public EinzelKategorie[] getKategorien(final boolean unterkategorienVerwenden) { if (unterkategorienVerwenden) { return this.kategorieListe.toArray(new EinzelKategorie[this.kategorieListe.size()]); } // Es sollen nur die Hauptkategorien ausgegeben werden: final int anzahl = this.kategorieListe.size(); final EinzelKategorie[] kategorien = new EinzelKategorie[anzahl]; int katZaehler = 0; for (int i = 0; i < anzahl; i++) { final EinzelKategorie kategorie = this.kategorieListe.get(i); if (kategorie.isHauptkategorie()) { kategorien[katZaehler++] = kategorie; } } final EinzelKategorie[] haupt = new EinzelKategorie[katZaehler]; System.arraycopy(kategorien, 0, haupt, 0, katZaehler); return haupt; } /** * Ersetzt in allen Registern die angegebne IKategorie durch eine neue. * * @param alteKategorie * @param neueKategorie * @return Anzahl ersetzter Kategorien */ public int ersetzeKategorie(final EinzelKategorie alteKategorie, final EinzelKategorie neueKategorie) { int zaehler = 0; for (int i = 0; i < this.registerListe.size(); i++) { final Register register = this.registerListe.get(i); for (int j = 0; j < register.getAnzahlBuchungen(); j++) { zaehler += register.getBuchung(j).ersetzeKategorie(alteKategorie, neueKategorie); } } this.geaendert = true; this.cacheAktuell = false; return zaehler; } /** * Prüft, ob der Registername schon existiert und hängt ggf. Ziffern an. * * @param regname * gewünschter Name * @return generierter Name */ private String generiereRegistername(final String regname) { String generierterName = regname; boolean nameVorhanden; int count = 0; do { nameVorhanden = false; for (int i = 0; i < this.registerListe.size(); i++) { if (generierterName.equalsIgnoreCase("" + this.registerListe.get(i))) { nameVorhanden = true; } } if (nameVorhanden) { generierterName = regname + " (" + ++count + ")"; } } while (nameVorhanden); return generierterName; } /** * Erzeugt ein Register mit dem angegebenen Namen. * Wenn es schon ein Register mit gleichem Namen gibt, wird ein neues * Register * mit fortlaufender Nummerierung erzeugt. * * @param regname * Name des neuen Registers * @return tatsächlich verwendeter Name */ public String erzeugeRegister(final String regname) { final String generierterName = generiereRegistername(regname); final Register register = new Register(generierterName); this.registerListe.add(register); if (DEBUG) { LOGGER.info("Register " + generierterName + " erzeugt."); } this.geaendert = true; return generierterName; } /** * Liefert das passende Register zum angegebenen Namen. * * @param regname * Name des gesuchen Registers * @return gesuchtes Register */ private Register findeRegister(final String regname) { for (int i = 0; i < this.registerListe.size(); i++) { if (regname.equals("" + this.registerListe.get(i))) { return this.registerListe.get(i); } } return null; } /** * Liefert das passende Register zum angegebenen Namen. * Wenn das Register noch nicht existiert, wird es erzeugt. * * @param regname * Name des gesuchen Registers * @return gesuchtes oder erzeugtes Register */ public Register findeOderErzeugeRegister(final String regname) { Register register = findeRegister(regname); if (register == null) { erzeugeRegister(regname); register = findeRegister(regname); } return register; } /** * Umbenennen eines Register. * * @param alterName * @param neuerName */ public String renameRegister(final String alterName, final String neuerName) { final Register altesRegister = findeRegister(alterName); final String generierterName = generiereRegistername(neuerName); altesRegister.setName(generierterName); this.geaendert = true; return generierterName; } /** * Verschiebt ein Register innerhalb der Register-Liste. * * @param register * Register * @param indexNeu * Neuer Index des Registers */ private void aendereRegisterIndex(final Register register, final int indexNeu) { this.registerListe.remove(register); this.registerListe.add(indexNeu, register); if (DEBUG) { for (int i = 0; i < this.registerListe.size(); i++) { LOGGER.info("" + this.registerListe.get(i)); } } } /** * Verschiebt ein Register innerhalb der Register-Liste. * * @param regname * Name des Registers * @param indexNeu * Neuer Index des Registers */ public void aendereRegisterIndex(final String regname, final int indexNeu) { Register register = null; for (int i = 0; i < this.registerListe.size(); i++) { if (regname.equals("" + this.registerListe.get(i))) { register = this.registerListe.get(i); } } if (register != null) { aendereRegisterIndex(register, indexNeu); } } /** * Liefert eine Liste mit alle Register-Namen. * Diese wird für die Combo-Boxen zur Auswahl eines Registers benötigt. * * @return Array mit den Register-Namen */ public String[] getRegisterNamen() { final int anzahl = this.registerListe.size(); final String[] namen = new String[anzahl]; for (int i = 0; i < anzahl; i++) { namen[i] = "" + this.registerListe.get(i); } return namen; } /** * Überträgt die Buchungen aus dem Quell-Register ins Ziel-Register. * Das Quell-Register wird anschließend gelöscht. * * @param quelle * Name des Quell-Registers * @param ziel * Name des Ziel-Registers */ public void registerVereinigen(final String quelle, final String ziel) { final Register quellRegister = findeRegister(quelle); final Register zielRegister = findeRegister(ziel); zielRegister.registerVereinigen(quellRegister); this.registerListe.remove(quellRegister); this.geaendert = true; } // -- Buchungen --------------------------------------------- /** * Erzeugt im angegebenen Register eine neue Buchung. * Dies ist in der Regel die noch leere Buchung am Ende des Registers. * * @param regname * Name des Registers * @param buchung * Neue Buchung * @return Index an der die neue Buchung in das Register eingefügt wurde */ public int addStandardBuchung(final String regname, final StandardBuchung buchung) { final Register register = findeRegister(regname); this.geaendert = true; this.cacheAktuell = false; return register.einsortierenBuchung(buchung); } /** * Erzeugt eine Umbuchung und fügt diese im Quell- und Zielregister ein. * * @param datum * Buchungdatum * @param buchungstext * Buchungstext * @param quelle * Quellregister * @param ziel * Zielregister * @param betrag * Betrag */ public void addUmbuchung( final Datum datum, final String buchungstext, final String quelle, final String ziel, final Euro betrag) { final UmbuchungKategorie kategorie = new UmbuchungKategorie(findeRegister(quelle), findeRegister(ziel)); new Umbuchung(datum, buchungstext, kategorie, betrag); // beim Erzeugen der Umbuchung wird diese automatisch in die beiden // Register // eingefügt. this.geaendert = true; } /** * Liefert die Anzahl der Buchungen im angegebenen Register. * * @param regname * Name des Registers * @return Anzahl der Buchungen */ public int getAnzahlBuchungen(final String regname) { final Register register = findeRegister(regname); if (register == null) { if (DEBUG) { LOGGER.info("Datenbasis.getAnzahlBuchungen: " + regname + " gibt es nicht."); } return 0; } return register.getAnzahlBuchungen(); } /** * Entfernt eine Buchung aus einem Register. * * @param regname * Name des Registers * @param index * Position der Buchung */ public void entferneBuchung(final String regname, final int index) { final Register register = findeRegister(regname); register.entferneBuchung(index); this.geaendert = true; this.cacheAktuell = false; } /** * Ersetzt eine Buchung durch eine andere. * * @param regname * Name des Registers * @param index * Position der Buchung * @param buchung * Neue Buchung * @return Einfüge-Position */ public int ersetzeBuchung(final String regname, final int index, final AbstractBuchung buchung) { final Register register = findeRegister(regname); register.entferneBuchung(index); this.geaendert = true; this.cacheAktuell = false; return register.einsortierenBuchung(buchung); } /** * Liefert eine Buchung aus einem Register. * * @param regname * Name des Registers * @param index * Position der Buchung */ public AbstractBuchung getBuchung(final String regname, final int index) { final Register register = findeRegister(regname); return register.getBuchung(index); } /** * Sortiert die angegebene Buchung neu ins Register ein. * Dies wird notwendig, wenn das Datum der Buchung geändert wurde. * * @param regname * Name des Registers * @param buchung * geänderte Buchung * @return Einfüge-Position */ public int buchungNeusortieren(final String regname, final AbstractBuchung buchung) { final Register register = findeRegister(regname); return register.buchungNeusortieren(buchung); } /** * Löscht alle Buchungen vor dem angegebenen Datum. * * @param datum * Datum */ public void entferneAlteBuchungen(final Datum datum) { final int anzahlRegister = this.registerListe.size(); final int[] pos = new int[anzahlRegister]; final Euro[] salden = new Euro[anzahlRegister]; // Erstmal die Salden aller Register ermitteln ... for (int i = 0; i < anzahlRegister; i++) { final Register register = this.registerListe.get(i); pos[i] = -1; // -1 = keine Buchungen löschen for (int j = 0; j < register.getAnzahlBuchungen(); j++) { if (register.getBuchung(j).compareTo(datum) <= 0) { pos[i] = j; } } if (pos[i] > -1) { salden[i] = register.getSaldo(pos[i]); } } // ... und dann ggf. alte Buchungen löschen und Selbstbuchung einfügen. for (int i = 0; i < anzahlRegister; i++) { if (pos[i] > -1) { final Register register = this.registerListe.get(i); register.removeBisBuchung(pos[i]); new Umbuchung( (Datum) datum.clone(), RES.getString("opening_balance"), new UmbuchungKategorie(register, register), salden[i]); // Umbuchung werden automatisch einsortiert if (DEBUG) { LOGGER.info("-I- Im Register " + register + " wurden " + pos[i] + " Buchungen gelöscht!"); } } } this.geaendert = true; this.cacheAktuell = false; } /** * Beginnt die Suche wieder in der ersten Buchung des ersten Registers. * */ public void resetSuchIdx() { this.registerSuchIdx = 0; this.buchungSuchIdx = 0; } /** * Sucht nach dem angegebenen Text in allen Buchungen. * * @param text * gesuchter Text * @return Buchung in der der Text gefunden wurde */ public AbstractBuchung suchen(final String text, final boolean grossUndKlein) { for (int i = this.registerSuchIdx; i < this.registerListe.size(); i++) { final Register register = this.registerListe.get(i); for (int j = this.buchungSuchIdx; j < register.getAnzahlBuchungen(); j++) { final AbstractBuchung buchung = register.getBuchung(j); if (buchung.sucheText(text, grossUndKlein)) { this.buchungSuchIdx = j + 1; // da geht es weiter return buchung; } } this.buchungSuchIdx = 0; this.registerSuchIdx++; } this.registerSuchIdx = 0; return null; } /** * Liefert den Namen des Registers in der der Text gefunden wurde. * * @return Register-Name */ public String getRegisterGefundenerText() { return "" + this.registerListe.get(this.registerSuchIdx); } /** * Liefert den Index der Buchung in der der Text gefunden wurde. * * @return Index der Buchung */ public int getBuchNrGefundenerText() { return this.buchungSuchIdx - 1; // der Suchindex ist schon eine Position // weiter } // -- Auswertungen ------------------------------------------ /** * Liefert alle Buchungen einer IKategorie mit einem Buchungstext * * @param buchungstext * Buchungstext * @param kategorie * IKategorie * @param unterkategorienVerwenden * Die Unterkategorien sollen verwendet werden * @return Liste der Buchungen */ public ArrayList<Datensatz> getBuchungen( final String buchungstext, final EinzelKategorie kategorie, final Boolean unterkategorienVerwenden) { final ArrayList<Datensatz> liste = new ArrayList<Datensatz>(); for (int i = 0; i < this.registerListe.size(); i++) { final Register register = this.registerListe.get(i); for (int j = 0; j < register.getAnzahlBuchungen(); j++) { final AbstractBuchung buchung = register.getBuchung(j); if ((buchung.getText().contains(buchungstext)) && (buchung.getClass() != Umbuchung.class) && ((kategorie == null) || buchung.istInKategorie(kategorie, unterkategorienVerwenden))) { liste.add(new Datensatz(register, buchung)); } } } return liste; } /** * Liefert alle Buchungen * * @return Liste der Buchungen */ public ArrayList<String[]> getBuchungen() { final ArrayList<String[]> liste = new ArrayList<String[]>(); for (int i = 0; i < this.registerListe.size(); i++) { final Register register = this.registerListe.get(i); for (int j = 0; j < register.getAnzahlBuchungen(); j++) { final AbstractBuchung buchung = register.getBuchung(j); final String[] zeile = { "" + buchung.getDatum(), buchung.getText(), "" + buchung.getKategorie(), "" + buchung.getWert(), "" + register}; liste.add(zeile); } } return liste; } /** * Liefert alle Buchungen einer IKategorie in einem Zeitraum. * * @param zeitraum * Zeitraum * @param regname * Name des Registers * @param kategorien * Liste mit Kategorien * @return Liste der Buchungen */ public ArrayList<String[]> getBuchungen( final AbstractZeitraum zeitraum, final String regname, final EinzelKategorie[] kategorien, final boolean unterkategorienVerwenden) { final ArrayList<String[]> liste = new ArrayList<String[]>(); if (regname == null) { for (int i = 0; i < this.registerListe.size(); i++) { getBuchungen(liste, zeitraum, this.registerListe.get(i), kategorien, unterkategorienVerwenden); } } else { getBuchungen(liste, zeitraum, findeRegister(regname), kategorien, unterkategorienVerwenden); } return liste; } private void getBuchungen( final ArrayList<String[]> liste, final AbstractZeitraum zeitraum, final Register register, final EinzelKategorie[] kategorien, final boolean unterkategorienVerwenden) { for (int i = 0; i < register.getAnzahlBuchungen(); i++) { final AbstractBuchung buchung = register.getBuchung(i); final Datum datum = buchung.getDatum(); if (datum.istImZeitraum(zeitraum)) { for (int j = 0; j < kategorien.length; j++) { final Euro wert = buchung.getKategorieWert(kategorien[j], unterkategorienVerwenden); if (!wert.equals(Euro.NULL_EURO)) { final int anzahl = liste.size(); int pos = -1; for (int k = 0; k < anzahl; k++) { if (datum.compareTo(new Datum(liste.get(k)[0])) >= 0) { pos = k; } } final String[] zeile = {"" + datum, buchung.getText(), "" + kategorien[j], "" + wert}; if (pos == anzahl - 1) { liste.add(zeile); } else { // neue Buchung einfuegen liste.add(pos + 1, zeile); } } } } } } /** * Summiert alle Kategorien mit positivem Saldo im angegebenen * Register und Zeitraum auf. * * @param regname * Name der Registers, <code>null</code> = alle Register * @param zeitraum * Zeitraum * @return Einnahmen */ public Euro getEinnahmen(final AbstractZeitraum zeitraum, final String regname) { final Euro einnahmen = new Euro(); erneuereKategorieCache(zeitraum, regname, true); for (int i = 0; i < this.kategorieListe.size(); i++) { final Euro wert = (this.kategorieListe.get(i)).getSumme(); if (wert.compareTo(Euro.NULL_EURO) > 0) { einnahmen.sum(wert); } } return einnahmen; } /** * Summiert alle Kategorien mit negativem Saldo im angegebenen * Register und Zeitraum auf. * * @param regname * Name der Registers, <code>null</code> = alle Register * @param zeitraum * Zeitraum * @return Ausgaben */ public Euro getAusgaben(final AbstractZeitraum zeitraum, final String regname) { final Euro ausgaben = new Euro(); erneuereKategorieCache(zeitraum, regname, true); for (int i = 0; i < this.kategorieListe.size(); i++) { final Euro wert = (this.kategorieListe.get(i)).getSumme(); if (wert.compareTo(Euro.NULL_EURO) < 0) { ausgaben.sum(wert); } } return Euro.NULL_EURO.sub(ausgaben); } /** * Ermittelt den Saldo an einem bestimmten Datum über alle Register. * * @param datum * Datum des gesuchten Saldos * @return gesuchter Saldo */ public Euro getSaldo(final Datum datum) { final Euro saldo = new Euro(); for (int i = 0; i < this.registerListe.size(); i++) { final Register register = this.registerListe.get(i); saldo.sum(register.getSaldo(datum)); } return saldo; } /** * Liefert den Saldo an einer Position im Register. * * @param regname * Name des Registers * @param index * Position */ public Euro getRegisterSaldo(final String regname, final int index) { final Register register = findeRegister(regname); return register.getSaldo(index); } /** * Liefert den Saldo an einem bestimmten Datum in einem bestimmten Register. * * @param regname * Name der Registers * @param datum * Datum des gesuchten Saldos * @return gesuchter Saldo */ public Euro getRegisterSaldo(final String regname, final Datum datum) { final Register register = findeRegister(regname); return register.getSaldo(datum); } /** * Ermittelt den Saldo über EINE IKategorie in einem bestimmten Register und * Zeitraum. * * @param regname * Name des Registers, <code>null</code> = alle Register * @param kategorie * Name der IKategorie * @param zeitraum * Zeitraum * @return gesuchter Saldo */ public Euro getKategorieSaldo( final EinzelKategorie kategorie, final AbstractZeitraum zeitraum, final String regname, final boolean unterkategorienVerwenden) { erneuereKategorieCache(zeitraum, regname, unterkategorienVerwenden); if (DEBUG) { LOGGER.info("" + kategorie + ": Saldo @ " + zeitraum + " = " + kategorie.getSumme()); } return kategorie.getSumme(); } /** * Liefert die Salden für ALLE Kategorien in einem Zeitraum. * * @param zeitraum * Zeitraum * @return Salden aller Kategorien */ public Euro[] getKategorieSalden(final AbstractZeitraum zeitraum, final boolean unterkategorienVerwenden) { erneuereKategorieCache(zeitraum, null, unterkategorienVerwenden); if (unterkategorienVerwenden) { final int anzahlKategorien = this.kategorieListe.size(); final Euro[] summen = new Euro[anzahlKategorien]; for (int i = 0; i < anzahlKategorien; i++) { summen[i] = (this.kategorieListe.get(i)).getSumme(); } return summen; } // Nur die Hauptkategorien: return getKategorieSalden(getKategorien(true), zeitraum, null, false); // Alle Kategorien werden übergeben, aber nur die Hauptkategorien // aufgerufen } /** * Liefert die Salden für BESTIMMTE Kategorien in einem Zeitraum. * * @param kategorien * Kategorien * @param zeitraum * Zeitraum * @param regname * Name der Registers, <code>null</code> = alle Register * @return Salden der Kategorien */ public Euro[] getKategorieSalden( final EinzelKategorie[] kategorien, final AbstractZeitraum zeitraum, final String regname, final boolean unterkategorienVerwenden) { erneuereKategorieCache(zeitraum, regname, unterkategorienVerwenden); final Euro[] summen = new Euro[kategorien.length]; for (int i = 0; i < kategorien.length; i++) { summen[i] = kategorien[i].getSumme(); } return summen; } /** * Die Summen der Kategorien werden in einem Cache gehalten, * da sich aufeinanderfolgende Abfragen häufig auf die * gleichen Daten beziehen. * Um zu überprüfen, ob der Cache noch aktuell ist, wird * getestet ob * (1) Zeitraum und (2) Registername dem Cache entsprechen * (3) der Cache noch aktuell ist und ob * (4) die Verwendung der Unterkategorien übereinstimmt * * @param zeitraum * Zeitraum der Abfrage * @param regname * Register der Abfrage * @param unterkategorienVerwenden */ private void erneuereKategorieCache( final AbstractZeitraum zeitraum, final String regname, final boolean unterkategorienVerwenden) { if (((zeitraum == this.zeitraumImCache) || ((zeitraum != null) && zeitraum.equals(this.zeitraumImCache))) && ((regname == this.registerImCache) || ((regname != null) && regname.equals(this.registerImCache))) && (unterkategorienVerwenden == this.cacheMitUnterkategorien) && this.cacheAktuell) { setCacheHit(getCacheHit() + 1); return; } setCacheMiss(getCacheMiss() + 1); this.zeitraumImCache = zeitraum; this.registerImCache = regname; this.cacheMitUnterkategorien = unterkategorienVerwenden; this.cacheAktuell = true; final int anzahlKategorien = this.kategorieListe.size(); final int anzahlRegister = this.registerListe.size(); for (int i = 0; i < anzahlKategorien; i++) { final EinzelKategorie kategorie = this.kategorieListe.get(i); kategorie.loescheSumme(); } if (regname == null) { // = ALLE Register for (int i = 0; i < anzahlRegister; i++) { final Register reg = this.registerListe.get(i); final int anzahlBuchungen = reg.getAnzahlBuchungen(); for (int j = 0; j < anzahlBuchungen; j++) { reg.getBuchung(j).bildeKategorieSumme(zeitraum, unterkategorienVerwenden); } } } else { final Register register = findeRegister(regname); final int anzahlBuchungen = register.getAnzahlBuchungen(); for (int j = 0; j < anzahlBuchungen; j++) { register.getBuchung(j).bildeKategorieSumme(zeitraum, unterkategorienVerwenden); } } } /** * Setzt das Start-Datum, ab dem Buchungen gemerkt werden. * * @param datum * Start-Datum */ public void setStartDatum(final Datum datum) { this.startDatumGemerkteBuchungen = datum; if (DEBUG) { LOGGER.info("Neues Startdatum: " + datum); } } /** * Merkt sich die Standard- und SplitBuchungen. Falls es den Buchungstext * schon * gibt wird die zuvor gemerkte Buchung überschrieben. * * @param buchung * Buchung zum Merken */ public void buchungMerken(final AbstractBuchung buchung) { if (buchung.getClass() == Umbuchung.class) { return; } // normale Buchungen merken (keine Umbuchungen!) if ((this.startDatumGemerkteBuchungen.compareTo(buchung.getDatum()) > 0)) { return; } // Buchung war zu alt zum Merken :-) final int pos = Collections.binarySearch(this.gemerkteBuchungenText, buchung.getText()); if (pos < 0) { // so eine Buchung gibt es noch nicht: Einfuegen this.gemerkteBuchungen.add(-pos - 1, buchung); this.gemerkteBuchungenText.add(-pos - 1, buchung.getText()); } else { // Buchung gibt es schon: Ueberschreiben this.gemerkteBuchungen.set(pos, buchung); this.gemerkteBuchungenText.set(pos, buchung.getText()); } if (DEBUG) { LOGGER.info("-- Gemerkte Buchungen:"); for (int i = 0; i < this.gemerkteBuchungen.size(); i++) { final AbstractBuchung dumpBuchung = this.gemerkteBuchungen.get(i); LOGGER.info("" + dumpBuchung.getText() + " / " + dumpBuchung.getKategorie()); } } } /** * Liefert eine gemerkte Buchung, die mit dem angegebenen String beginnt. * * @param prefix * Anfang des gesuchten Buchungstextes * @return gefundene Buchung oder <code>null</code>, wenn nichts gefunden */ public AbstractBuchung findeGemerkteBuchung(final String prefix) { for (int i = 0; i < this.gemerkteBuchungenText.size(); i++) { if (this.gemerkteBuchungenText.get(i).startsWith(prefix)) { return this.gemerkteBuchungen.get(i); } } return null; } public static int getCacheHit() { return cacheHit; } public static void setCacheHit(final int hit) { cacheHit = hit; } public static int getCacheMiss() { return cacheMiss; } public static void setCacheMiss(final int miss) { cacheMiss = miss; } private Integer getLegacyIntervallIndex(final String name) { for (int i = 0; i < LEGACY_INTERVALL_NAMEN.length; i++) { if (name.equals(LEGACY_INTERVALL_NAMEN[i])) { return i; } } return 0; } /** * Liefert die Anzahl der vorhandenen wiederkehrenden Standard-Buchungen. * * @return Anzahl */ public int getAnzahlAutoStandardBuchungen() { return this.autoStandardBuchungen.size(); } /** * Liefert die Anzahl der vorhandenen wiederkehrenden Umbuchungen. * * @return Anzahl */ public int getAnzahlAutoUmbuchungen() { return this.autoUmbuchungen.size(); } /** * Liefert eine wiederkehrende Standard-Buchung. * * @param index * Nummer der Buchung * @return Buchung */ public StandardBuchung getAutoStandardBuchung(final int index) { return this.autoStandardBuchungen.get(index); } /** * Liefert eine wiederkehrende Umuchung. * * @param index * Nummer der Buchung * @return Buchung */ public Umbuchung getAutoUmbuchung(final int index) { return this.autoUmbuchungen.get(index); } /** * Liefert das Register zu einer wiederkehrenden Standard-Buchung. * * @param index * Nummer der Buchung * @return Name des Registers */ public String getAutoStandardBuchungRegister(final int index) { return "" + this.autoStandardBuchungRegister.get(index); } /** * Liefert die Register zu einer wiederkehrenden Umbuchung. * * @param index * Nummer der Buchung * @return Register-Paar */ public UmbuchungKategorie getAutoUmbuchungRegister(final int index) { return this.autoUmbuchungRegister.get(index); } /** * Setzt das Register zu einer wiederkehrenden Standard-Buchung. * * @param index * Nummer der Buchung * @param register * Name des Registers */ public void setAutoStandardBuchungRegister(final int index, final String register) { this.autoStandardBuchungRegister.set(index, findeRegister(register)); this.geaendert = true; } /** * Setzt das Register zu einer wiederkehrenden Umbuchung. * * @param index * Nummer der Buchung * @param register * Register-Paar */ public void setAutoUmbuchungRegister(final int index, final UmbuchungKategorie register) { this.autoUmbuchungRegister.set(index, register); this.geaendert = true; } /** * Liefert den Index des Intervalls (Woche, Monat, Quartal, Halbjahr, Jahr) * zu einer wiederkehrenden Standard-Buchung. * * @param index * Nummer der Buchung * @return Name des Intervall */ public Integer getAutoStandardBuchungIntervall(final int index) { return this.autoStandardBuchungIntervalle.get(index); } /** * Liefert den Index des Intervalls (Woche, Monat, Quartal, Halbjahr, Jahr) * zu einer wiederkehrenden Umuchung. * * @param index * Nummer der Buchung * @return Name des Intervall */ public Integer getAutoUmbuchungIntervall(final int index) { return this.autoUmbuchungIntervalle.get(index); } /** * Setzt das Intervall (Monat, Quartal, Halbjahr, Jahr) * zu einer wiederkehrenden Standard-Buchung. * * @param index * Nummer der Buchung * @param intervallIndex * Index des Intervalls */ public void setAutoStandardBuchungIntervall(final int index, final Integer intervallIndex) { this.autoStandardBuchungIntervalle.set(index, intervallIndex); this.geaendert = true; } /** * Setzt das Intervall (Monat, Quartal, Halbjahr, Jahr) * zu einer wiederkehrenden Umbuchung. * * @param index * Nummer der Buchung * @param intervallIndex * Index des Intervalls */ public void setAutoUmbuchungIntervall(final int index, final Integer intervallIndex) { this.autoUmbuchungIntervalle.set(index, intervallIndex); this.geaendert = true; } /** * Erzeugt eine neue wiederkehrenden Standard-Buchung. * Dies geschied, wenn in der letzten Zeile der Tabelle eine Eingabe * erfolgt. */ public void addAutoStandardBuchung() { this.autoStandardBuchungen.add(new StandardBuchung()); this.autoStandardBuchungRegister.add(this.registerListe.get(0)); this.autoStandardBuchungIntervalle.add(0); this.geaendert = true; } /** * Erzeugt eine neue wiederkehrenden Umbuchung. * Dies geschied, wenn in der letzten Zeile der Tabelle eine Eingabe * erfolgt. */ public void addAutoUmbuchung() { // Erzeugen einer Dummy-Umbuchung; Quell- und Ziel-Register // sind das erste Register der Liste // Problem: Automatisches Einfügen der Umbuchung ==> getrennte // Speicherung des Register-Paars this.autoUmbuchungen.add(new Umbuchung()); this.autoUmbuchungRegister.add(new UmbuchungKategorie(this.registerListe.get(0), this.registerListe.get(0))); this.autoUmbuchungIntervalle.add(0); this.geaendert = true; } /** * Löscht eine wiederkehrende Standard-Buchung. * * @param index * Nummer der Buchung */ public void entferneAutoStandardBuchung(final int index) { if (index < this.autoStandardBuchungen.size()) { this.autoStandardBuchungen.remove(index); this.autoStandardBuchungRegister.remove(index); this.autoStandardBuchungIntervalle.remove(index); this.geaendert = true; } } /** * Löscht eine wiederkehrende Umbuchung. * * @param index * Nummer der Buchung */ public void entferneAutoUmbuchung(final int index) { if (index < this.autoUmbuchungen.size()) { this.autoUmbuchungen.remove(index); this.autoUmbuchungIntervalle.remove(index); this.geaendert = true; } } /** * Fügt die fälligen wiederkehrenden Buchungen in das entsprechende Register * ein. * * @param datum * Datum bis zum die Buchungen ausgeführt werden sollen * @return Anzahl der eingefügten Buchungen */ public int ausfuehrenAutoBuchungen(final Datum datum) { int zaehler = 0; // 1. Standard-Buchungen: for (int i = 0; i < this.autoStandardBuchungen.size(); i++) { final StandardBuchung buchung = this.autoStandardBuchungen.get(i); final Register register = this.autoStandardBuchungRegister.get(i); while (buchung.getDatum().compareTo(datum) <= 0) { register.einsortierenBuchung((StandardBuchung) buchung.clone()); if (DEBUG) { LOGGER.info("AutoBuchung ausgeführt: " + buchung.getText()); } final Integer intervall = this.autoStandardBuchungIntervalle.get(i); buchung.setDatum(getFolgeDatum(buchung.getDatum(), intervall)); this.autoStandardBuchungen.set(i, buchung); zaehler++; } } // 2. Umbuchungen: for (int i = 0; i < this.autoUmbuchungen.size(); i++) { final Umbuchung buchung = this.autoUmbuchungen.get(i); final UmbuchungKategorie umbuchungKategorie = this.autoUmbuchungRegister.get(i); while (buchung.getDatum().compareTo(datum) <= 0) { final Umbuchung neueUmbuchung = (Umbuchung) buchung.clone(); neueUmbuchung.setKategorie((UmbuchungKategorie) umbuchungKategorie.clone()); // das Einsortieren ist damit automatisch erfolgt if (DEBUG) { LOGGER.info("AutoBuchung ausgeführt: " + buchung.getText()); } final Integer intervall = this.autoUmbuchungIntervalle.get(i); buchung.setDatum(getFolgeDatum(buchung.getDatum(), intervall)); zaehler++; } } if (zaehler > 0) { this.cacheAktuell = false; } return zaehler; } /** * Berechnet das nächste Datum an dem die wiederkehrende Buchung ausgeführt * werden soll. * * @param datum * altes Datum * @param intervall * Index des Intervalls in den die Buchung ausgeführt wird * @return Neues Datum */ private static Datum getFolgeDatum(final Datum datum, final Integer intervall) { int tag = datum.getTag(); int monat = datum.getMonat(); int jahr = datum.getJahr(); switch (intervall) { case 0: tag += 7; break; // Woche case 1: monat += 1; break; // Monat case 2: monat += 3; break; // Quartal case 3: monat += 6; break; // Halbjahr default: jahr += 1; break; // Jahr } if (monat > 12) { monat -= 12; jahr++; } // der mögliche Überlauf beim Tag wird in der Klasse // 'Datum' korrigiert return new Datum(tag, monat, jahr); } // -- Import // ---------------------------------------------------------------- /** * Importiert eine CSV-Datei (Comma Separated Value) in das angegebene * Register. * Die übergebene Tabelle muss (mindestens) 4 Spalten besitzen. * * @param regname * Name des Registers * @param tabelle * Tabelle */ public void importBuchungen(final String regname, final String[][] tabelle) { final Register register = findeRegister(regname); for (int i = 0; i < tabelle.length; i++) { final Datum datum = new Datum(tabelle[i][0]); final String text = tabelle[i][1]; final EinzelKategorie kategorie = findeOderErzeugeKategorie(tabelle[i][2]); final Euro wert = new Euro(tabelle[i][3]); final AbstractBuchung buchung = new StandardBuchung(datum, text, kategorie, wert); register.einsortierenBuchung(buchung); this.geaendert = true; this.cacheAktuell = false; } } /** * Importiert eine Quicken-Export-Datei (QIF) in das angegebene Register. * * @param in * Eingabe-Stream * @param regname * Name des Registers * @param dm * <code>true</code> wenn die Währung der Daten D-Mark ist. */ public void importQuickenRegister(final InputStream in, final String regname, final boolean dm) throws IOException, QuickenImportException { final Register register = findeRegister(regname); final String typ = leseQIFZeile(in); if (DEBUG) { LOGGER.info("-I- QuickenImport: " + typ); } int c; while ((c = in.read()) != -1) { // EOF erreicht // Datum einlesen if (c != 'D') { throw new QuickenImportException("-E- QuickenImport: Ups! D (Datum) erwartet."); } final StringTokenizer st = new StringTokenizer(leseQIFZeile(in), "."); String monat = st.nextToken(); if (monat.length() == 1) { monat = "0" + monat; } String tag = st.nextToken(); if (tag.length() == 1) { tag = "0" + tag; } final String jahr = st.nextToken(); // TODO Import überarbeiten: Funktioniert nur mit deutschem Datum! final Datum datum = new Datum(tag + "." + monat + "." + jahr); // Betrag einlesen if (in.read() != 'U') { throw new QuickenImportException("-E- QuickenImport: Ups! U (Betrag) erwartet."); } final Euro einzelWert = new Euro(leseQIFZeile(in).replace('.', ',')); // US // -> // deutsch if (in.read() != 'T') { throw new QuickenImportException("-E- QuickenImport: Ups! T (Betrag) erwartet."); } if (!einzelWert.equals(new Euro(leseQIFZeile(in).replace('.', ',')))) { throw new QuickenImportException("-E- QuickenImport: Ups! U und T Betrag ungleich."); } if (dm) { einzelWert.umrechnenVonDM(); } // Abgeglichene Buchung "X" einlesen c = in.read(); if (c == 'C') { leseQIFZeile(in); c = in.read(); } // Buchungstext einlesen String text = "*"; if (c == 'P') { text = leseQIFZeile(in); c = in.read(); } // IKategorie einlesen String einzelKategorie; if (c == 'L') { einzelKategorie = leseQIFZeile(in); c = in.read(); } else { einzelKategorie = "" + EinzelKategorie.SONSTIGES; } // Split-Buchungen einlesen StandardBuchung standardBuchung = null; SplitBuchung splitBuchung = null; while (c == 'S') { final String kategorie = leseQIFZeile(in); if (in.read() != '$') { throw new QuickenImportException("-E- QuickenImport: Ups! $ (Betrag-Splitbuchung) erwartet."); } final Euro wert = new Euro(leseQIFZeile(in).replace('.', ',')); // US // -> // deutsch if (dm) { wert.umrechnenVonDM(); } // Prüfen, ob Umbuchung: if ((kategorie.length() > 2) && kategorie.startsWith("[") && kategorie.endsWith("]")) { if (wert.compareTo(Euro.NULL_EURO) > 0) { new Umbuchung(datum, text, new UmbuchungKategorie(findeOderErzeugeRegister(kategorie), register), wert); // Nur positive Umbuchungen erzeugen; Umbuchungen werden // automatisch eingefügt if (DEBUG) { LOGGER.info("Umbuchung " + text + " erzeugt."); } } } else { // keine Umbuchung: if (standardBuchung == null) { standardBuchung = new StandardBuchung(datum, text, findeOderErzeugeKategorie(kategorie), wert); } else if (splitBuchung == null) { splitBuchung = new SplitBuchung(standardBuchung); splitBuchung.add(findeOderErzeugeKategorie(kategorie), wert); } else { splitBuchung.add(findeOderErzeugeKategorie(kategorie), wert); } } c = in.read(); } /* Ende while Split-Buchung */ if (c != '^') { throw new QuickenImportException("-E- QuickenImport: Ups! ^ (Datensatzende) erwartet."); } leseQIFZeile(in); // CR+LF von '^' lesen if (splitBuchung != null) { register.einsortierenBuchung(splitBuchung); if (DEBUG) { LOGGER.info("SplitBuchung erzeugt."); } } else if (standardBuchung != null) { register.einsortierenBuchung(standardBuchung); if (DEBUG) { LOGGER.info("StandardBuchung erzeugt."); } } else if ((einzelKategorie.length() > 2) && // Pruefen ob Umbuchung einzelKategorie.startsWith("[") && einzelKategorie.endsWith("]")) { if (einzelWert.compareTo(Euro.NULL_EURO) > 0) { new Umbuchung( datum, text, new UmbuchungKategorie(findeOderErzeugeRegister(einzelKategorie), register), einzelWert); // Nur positive Umbuchungen erzeugen; Umbuchungen werden // automatisch eingefügt if (DEBUG) { LOGGER.info("Umbuchung " + text + " erzeugt."); } } } else { register.einsortierenBuchung(new StandardBuchung( datum, text, findeOderErzeugeKategorie(einzelKategorie), einzelWert)); if (DEBUG) { LOGGER.info("StandardBuchung " + text + " erzeugt."); } } } /* Ende while Datei einlesen */ this.cacheAktuell = false; this.geaendert = true; } public static class QuickenImportException extends Exception { private static final long serialVersionUID = 1L; public QuickenImportException(final String text) { super(text); } } /** * Liest eine einzelne Zeile aus der QIF-Datei (Quicken). * * @param in * Eingabe-Stream * @return gelesende Zeile * @throws IOException * @throws QuickenImportException */ private String leseQIFZeile(final InputStream in) throws IOException, QuickenImportException { String zeile = ""; int c = in.read(); while (c != 13) { // Bis zum Zeilenende (CR) if (c == -1) { throw new QuickenImportException("-E- QuickenImport: Ups! Unerwartetes EOF."); } if (c != ',') { zeile = zeile + (char) c; } } c = in.read(); if (c != 10) { throw new QuickenImportException("-E- QuickenImport: Ups! Nach CR kam kein LF. " + (char) c); } return zeile; } // -- E/A-Funktionen ------------------------------------------------------- /** * Lädt die Buchungen in die Register. Lädt und führt die * wiederkehrenden Buchungen. */ public void laden(final DataInputStream in, final String versionInfo) throws IOException { // Register laden final int anzahl = in.readInt(); for (int i = 0; i < anzahl; i++) { final Register register = findeOderErzeugeRegister(in.readUTF()); // Das Register kann schon zuvor (beim Laden eines anderen // Registers) erzeugt // worden sein, deshalb muss der Index des Registers explizit // gesetzt werden: aendereRegisterIndex(register, i); register.laden(in, this); } this.cacheAktuell = false; if (DEBUG) { LOGGER.info("" + anzahl + " Register geladen."); } // automatische Buchungen laden und ausführen (in v1.0 noch unbekannt!) if (!versionInfo.equals("jHaushalt1.0")) { final int size = in.readInt(); this.autoStandardBuchungen.ensureCapacity(size); for (int i = 0; i < size; i++) { // Buchung laden: final String typ = in.readUTF(); if (typ.equals("Umbuchung")) { final Umbuchung buchung = new Umbuchung(); buchung.laden(in, this, null); // Register laden: final Register quellRegister = findeOderErzeugeRegister(in.readUTF()); final Register zielRegister = findeOderErzeugeRegister(in.readUTF()); final UmbuchungKategorie registerPaar = new UmbuchungKategorie(quellRegister, zielRegister); // Intervall laden: final Integer zeitraum = getLegacyIntervallIndex(in.readUTF()); // Umbuchung einsortieren final int anz = this.autoUmbuchungen.size(); int pos = -1; for (int j = 0; j < anz; j++) { if (buchung.compareTo(this.autoUmbuchungen.get(j)) >= 0) { pos = j; } } if (pos == anz - 1) { // ans Ende this.autoUmbuchungen.add(buchung); this.autoUmbuchungRegister.add(registerPaar); this.autoUmbuchungIntervalle.add(zeitraum); } else { // neue Buchung einfuegen this.autoUmbuchungen.add(pos + 1, buchung); this.autoUmbuchungRegister.add(pos + 1, registerPaar); this.autoUmbuchungIntervalle.add(pos + 1, zeitraum); } } else if (!typ.equals("StandardBuchung") && !typ.equals("StandardBuchung2")) { throw new IOException("AutoBuchung: Falscher Buchungstyp: " + typ); } else { StandardBuchung buchung; if (typ.equals("StandardBuchung")) { // Laden des // Legacy-Formats final Datum datum = new Datum(); datum.laden(in); final String text = in.readUTF(); final int anz = in.readInt(); if (anz == 1) { final EinzelKategorie kategorie = findeOderErzeugeKategorie(in.readUTF()); final Euro betrag = new Euro(); betrag.laden(in); buchung = new StandardBuchung(datum, text, kategorie, betrag); } else { throw new IOException("AutoBuchung: Falscher Buchungstyp: SplitBuchung"); } } else { // Laden des aktuellen Formats für Standard-Buchungen buchung = new StandardBuchung(); (buchung).laden(in, this); } // Register laden: final Register register = findeOderErzeugeRegister(in.readUTF()); // Intervall laden: final Integer zeitraum = getLegacyIntervallIndex(in.readUTF()); // Buchung einsortieren final int anz = this.autoStandardBuchungen.size(); int pos = -1; for (int j = 0; j < anz; j++) { if (buchung.compareTo(this.autoStandardBuchungen.get(j)) >= 0) { pos = j; } } if (pos == anz - 1) { // ans Ende this.autoStandardBuchungen.add(buchung); this.autoStandardBuchungRegister.add(register); this.autoStandardBuchungIntervalle.add(zeitraum); } else { // neue Buchung einfuegen this.autoStandardBuchungen.add(pos + 1, buchung); this.autoStandardBuchungRegister.add(pos + 1, register); this.autoStandardBuchungIntervalle.add(pos + 1, zeitraum); } } } } this.geaendert = false; } /** * Speichert die Register mit den Buchungen und die wiederkehrenden Buchung. * * @param out * Ausgabe-Stream * @throws IOException */ public void speichern(final DataOutputStream out) throws IOException { // 1. Versionsinfo: out.writeUTF("jHaushalt" + VERSION_DATENBASIS); // 2. Buchungen (Kategorien werden NICHT gespeichert) out.writeInt(this.registerListe.size()); for (int i = 0; i < this.registerListe.size(); i++) { final Register register = this.registerListe.get(i); register.speichern(out); } // 3. automatische Buchungen out.writeInt(this.autoStandardBuchungen.size() + this.autoUmbuchungen.size()); // 3a. automatische Standard-Buchungen for (int i = 0; i < this.autoStandardBuchungen.size(); i++) { final AbstractBuchung buchung = this.autoStandardBuchungen.get(i); buchung.speichern(out); out.writeUTF("" + this.autoStandardBuchungRegister.get(i)); out.writeUTF(LEGACY_INTERVALL_NAMEN[this.autoStandardBuchungIntervalle.get(i)]); } // 3b. automatische Umbuchungen for (int i = 0; i < this.autoUmbuchungen.size(); i++) { final AbstractBuchung buchung = this.autoUmbuchungen.get(i); buchung.speichern(out); final UmbuchungKategorie registerPaar = this.autoUmbuchungRegister.get(i); out.writeUTF("" + registerPaar.getQuelle()); out.writeUTF("" + registerPaar.getZiel()); out.writeUTF(LEGACY_INTERVALL_NAMEN[this.autoUmbuchungIntervalle.get(i)]); } out.flush(); this.geaendert = false; } public void setFileName(String absolutePathAndFileName) { if (!absolutePathAndFileName.toLowerCase().endsWith(".jhh")) { absolutePathAndFileName += ".jhh"; } this.filename = absolutePathAndFileName; } public String getFilename() { return filename; } }