biz.wolschon.fileformats.gnucash.baseclasses.SimpleAccount.java Source code

Java tutorial

Introduction

Here is the source code for biz.wolschon.fileformats.gnucash.baseclasses.SimpleAccount.java

Source

/**
 * SimpleAccount.java
 * License: GPLv3 or later
 * created: 22.05.2006 17:56:15
 */
package biz.wolschon.fileformats.gnucash.baseclasses;

//other imports
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Currency;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import biz.wolschon.fileformats.gnucash.GnucashAccount;
import biz.wolschon.fileformats.gnucash.GnucashFile;
import biz.wolschon.fileformats.gnucash.GnucashTransaction;
import biz.wolschon.fileformats.gnucash.GnucashTransactionSplit;
import biz.wolschon.finance.ComplexCurrencyTable;
import biz.wolschon.numbers.FixedPointNumber;

/**
 * (c) 2006 by Wolschon Softwaredesign und Beratung.<br/>
 * Project: gnucashReader<br/>
 * SimpleAccount.java<br/>
 * created: 22.05.2006 17:56:15 <br/>
 *<br/><br/>
 * This is a base-class that helps implementing the GnucashAccount
 * -interface with it's extenive number of convenience-methods.<br/>
 * @author <a href="Marcus@Wolschon.biz">Marcus Wolschon</a>
 */
public abstract class SimpleAccount implements GnucashAccount {

    /**
      * Our logger for debug- and error-ourput.
      */
    private static final Log LOGGER = LogFactory.getLog(SimpleAccount.class);

    /**
     * The file we belong to.
     */
    private final GnucashFile myFile;

    /**
     * @param slots ${@link #mySlots}
     * @param myFile The file we belong to
     */
    public SimpleAccount(final GnucashFile myFile) {
        super();
        this.myFile = myFile;
    }

    /**
     * The returned list ist sorted by the natural order of the Transaction-Splits.
     * @return all splits
     * @see ${@link GnucashTransaction}
     */
    public List<GnucashTransaction> getTransactions() {
        List<? extends GnucashTransactionSplit> splits = getTransactionSplits();
        List<GnucashTransaction> retval = new ArrayList<GnucashTransaction>(splits.size());

        for (Object element : splits) {
            GnucashTransactionSplit split = (GnucashTransactionSplit) element;
            retval.add(split.getTransaction());
        }

        return retval;
    }

    /**
     * @return Returns the file.
     * @see ${@link #myFile}
     */
    public GnucashFile getGnucashFile() {
        return myFile;
    }

    /**
     * @param account the account to test
     * @return true if this is a child of us or any child's or us.
     */
    public boolean isChildAccountRecursive(final GnucashAccount account) {

        if (this == account) {
            return true;
        }

        for (Object element : getChildren()) {
            GnucashAccount child = (GnucashAccount) element;
            if (this == child) {
                return true;
            }
            if (child.isChildAccountRecursive(account)) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return getQualifiedName();
    }

    /**
     * same as getBalance(new Date()).<br/>
     * ignores transactions after the current date+time
     * @see #getBalance(Date)
     */
    public FixedPointNumber getBalance() {
        return getBalance(new Date());
    }

    /**
     * get name including the name of the parent.accounts.
     * @return e.g. "Aktiva::test::test2"
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getQualifiedName()
     */
    public String getQualifiedName() {
        GnucashAccount acc = getParentAccount();
        if (acc == null || acc.getId() == getId()) {
            if (getParentAccountId() == null) {
                return getName();
            }

            return "UNKNOWN::" + getName();
        }
        return acc.getQualifiedName() + "::" + getName();
    }

    /**
     *
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getParentAccount()
     */
    public GnucashAccount getParentAccount() {
        String id = getParentAccountId();
        if (id == null) {
            return null;
        }

        return getGnucashFile().getAccountByID(id);
    }

    /**
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getSubAccounts()
     */
    public Collection getSubAccounts() {
        return getChildren();
    }

    /**
     * @param date ignores transactions after the given date
     * @param currency the currency the result shall be in (use account-currency if null)
     * @return null if the conversion is not possible
     * @see #getBalance(Date)
     */
    public FixedPointNumber getBalance(final Date date, final Currency currency) {

        FixedPointNumber retval = getBalance(date);

        if (retval == null) {
            LOGGER.warn("SimpleAccount.getBalance() - " + "error creating balance!");
            return null;
        }

        if (currency == null || retval.equals(new FixedPointNumber())) {
            return retval;
        }

        // is conversion needed?
        if (getCurrencyNameSpace().equals(GnucashAccount.CURRENCYNAMESPACE_CURRENCY)
                && getCurrencyID().equals(currency.getCurrencyCode())) {
            return retval;
        }

        ComplexCurrencyTable currencyTable = getGnucashFile().getCurrencyTable();

        if (currencyTable == null) {
            LOGGER.warn("SimpleAccount.getBalance() - cannot transfer "
                    + "to given currency because we have no currency-table!");
            return null;
        }

        if (!currencyTable.convertToBaseCurrency(getCurrencyNameSpace(), retval, getCurrencyID())) {
            LOGGER.warn("SimpleAccount.getBalance() - cannot transfer " + "from our currency '"
                    + getCurrencyNameSpace() + "'-'" + getCurrencyID() + "' to the base-currency!");
            return null;
        }

        if (!currencyTable.convertFromBaseCurrency(retval, currency.getCurrencyCode())) {
            LOGGER.warn("SimpleAccount.getBalance() - cannot transfer " + "from base-currenty to given currency '"
                    + currency + "'!");
            return null;
        }

        return retval;
    }

    /**
    *
    * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalanceRecursiveFormated(java.util.Date)
    */
    public String getBalanceRecursiveFormated(final Date date) {
        return getCurrencyFormat().format(getBalanceRecursive(date));
    }

    /**
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalanceRecursiveFormated()
     */
    public String getBalanceRecursiveFormated() {
        return getCurrencyFormat().format(getBalanceRecursive());
    }

    /**
     *
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalanceRecursive()
     */
    public FixedPointNumber getBalanceRecursive() {

        return getBalanceRecursive(new Date());
    }

    /**
     *
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalanceRecursive(java.util.Date)
     */
    public FixedPointNumber getBalanceRecursive(final Date date) {

        return getBalanceRecursive(date, this.getCurrencyNameSpace(), this.getCurrencyID());
    }

    /**
     * Gets the last transaction-split before the given date.
     * @param date if null, the last split of all time is returned
     * @return the last transaction-split before the given date
     */
    public GnucashTransactionSplit getLastSplitBeforeRecursive(final Date date) {

        GnucashTransactionSplit lastSplit = null;

        for (Object element : getTransactionSplits()) {
            GnucashTransactionSplit split = (GnucashTransactionSplit) element;
            if (date == null || split.getTransaction().getDatePosted().before(date)) {
                if (lastSplit == null || split.getTransaction().getDatePosted()
                        .after(lastSplit.getTransaction().getDatePosted())) {
                    lastSplit = split;
                }
            }
        }

        for (Iterator iter = getSubAccounts().iterator(); iter.hasNext();) {
            GnucashAccount account = (GnucashAccount) iter.next();
            GnucashTransactionSplit split = account.getLastSplitBeforeRecursive(date);
            if (split != null && split.getTransaction() != null) {
                if (lastSplit == null || split.getTransaction().getDatePosted()
                        .after(lastSplit.getTransaction().getDatePosted())) {
                    lastSplit = split;
                }
            }
        }

        return lastSplit;
    }

    /**
     * Ignores accounts for wich this conversion is not possible.
     * @param date ignores transactions after the given date
     * @param currencyNameSpace the currency the result shall be in
     * @param currencyName the currency the result shall be in
     * @return Gets the balance including all sub-accounts.
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalanceRecursive(Date, Currency)
     */
    public FixedPointNumber getBalanceRecursive(final Date date, final String currencyNameSpace,
            final String currencyName) {

        FixedPointNumber retval = getBalance(date, currencyNameSpace, currencyName);

        if (retval == null) {
            retval = new FixedPointNumber();
        }

        for (Object element : getChildren()) {
            GnucashAccount child = (GnucashAccount) element;
            retval.add(child.getBalanceRecursive(date, currencyNameSpace, currencyName));
        }

        return retval;
    }

    /**
     * Ignores accounts for wich this conversion is not possible.
     * @param date ignores transactions after the given date
     * @param currency the currency the result shall be in
     * @return Gets the balance including all sub-accounts.
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalanceRecursive(Date, Currency)
     */
    public FixedPointNumber getBalanceRecursive(final Date date, final Currency currency) {

        FixedPointNumber retval = getBalance(date, currency);

        if (retval == null) {
            retval = new FixedPointNumber();
        }

        for (Object element : getChildren()) {
            GnucashAccount child = (GnucashAccount) element;
            retval.add(child.getBalanceRecursive(date, currency));
        }

        return retval;
    }

    /**
     * @return true if ${@link #hasTransactions()} is true for this
     *         or any sub-accounts
     */
    public boolean hasTransactionsRecursive() {
        if (this.hasTransactions()) {
            return true;
        }

        for (Object element : getChildren()) {
            GnucashAccount child = (GnucashAccount) element;
            if (child.hasTransactionsRecursive()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return true if ${@link #getTransactionSplits()}.size()>0
     */
    public boolean hasTransactions() {
        return this.getTransactionSplits().size() > 0;
    }

    /**
     * @param date ignores transactions after the given date
     * @param currencyNameSpace the currency the result shall be in
     * @param currencyName the currency the result shall be in
     * @return null if the conversion is not possible
     * @see #getBalance(Date)
     */
    public FixedPointNumber getBalance(final Date date, final String currencyNameSpace, final String currencyName) {
        FixedPointNumber retval = getBalance(date);

        if (retval == null) {
            LOGGER.warn("SimpleAccount.getBalance() - " + "error creating balance!");
            return null;
        }

        // is conversion needed?
        if (getCurrencyNameSpace().equals(currencyNameSpace) && getCurrencyID().equals(currencyName)) {
            return retval;
        }

        ComplexCurrencyTable currencyTable = getGnucashFile().getCurrencyTable();

        if (currencyTable == null) {
            LOGGER.warn("SimpleAccount.getBalance() - cannot transfer "
                    + "to given currency because we have no currency-table!");
            return null;
        }

        if (!currencyTable.convertToBaseCurrency(getCurrencyNameSpace(), retval, getCurrencyID())) {
            Collection<String> currencies = getGnucashFile().getCurrencyTable()
                    .getCurrencies(getCurrencyNameSpace());
            LOGGER.warn("SimpleAccount.getBalance() - cannot transfer " + "from our currency '"
                    + getCurrencyNameSpace() + "'-'" + getCurrencyID() + "' to the base-currency!" + " \n(we know "
                    + getGnucashFile().getCurrencyTable().getNameSpaces().size() + " currency-namespaces and "
                    + (currencies == null ? "no" : "" + currencies.size()) + " currencies in our namespace)");
            return null;
        }

        if (!currencyTable.convertFromBaseCurrency(currencyNameSpace, retval, currencyName)) {
            LOGGER.warn("SimpleAccount.getBalance() - cannot transfer " + "from base-currenty to given currency '"
                    + currencyNameSpace + "-" + currencyName + "'!");
            return null;
        }

        return retval;
    }

    /**
     * The currency-format to use for formating.<br/>
     * Plase access this only via {@link #getCurrencyFormat()}.
     */
    private static NumberFormat currencyFormat = null;

    /**
     * @return null if we are no currency but e.g. a fund
     */
    public Currency getCurrency() {

        if (!getCurrencyNameSpace().equals(GnucashAccount.CURRENCYNAMESPACE_CURRENCY)) {
            return null;
        }

        String gnucashCurrencyID = getCurrencyID();
        return Currency.getInstance(gnucashCurrencyID);
    }

    /**
     *
     * @return The currency-format to use for formating.
     */
    public NumberFormat getCurrencyFormat() {
        if (currencyFormat == null) {
            currencyFormat = NumberFormat.getCurrencyInstance();
        }

        // the currency may have changed
        if (this.getCurrencyNameSpace().equals(GnucashAccount.CURRENCYNAMESPACE_CURRENCY)) {
            Currency currency = getCurrency();
            currencyFormat.setCurrency(currency);
        } else {
            currencyFormat = NumberFormat.getNumberInstance();
        }

        return currencyFormat;
    }

    /**
     * same as {@link #getBalance(Date)}. <br/>
     * ignores transactions after the current date+time.
     * @see #getBalance(Date)
     */
    public String getBalanceFormated() {
        return getCurrencyFormat().format(getBalance());
    }

    /**
     * same as {@link #getBalance(Date)}. <br/>
     * ignores transactions after the current date+time.
     * @see #getBalance(Date)
     */
    public String getBalanceFormated(final Locale loc) {

        NumberFormat cf = NumberFormat.getCurrencyInstance(loc);
        cf.setCurrency(getCurrency());
        return cf.format(getBalance());
    }

    /**
     * The currency will be the one of this account.
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalance(java.util.Date)
     */
    public FixedPointNumber getBalance(final Date date) {
        return getBalance(date, (Collection<GnucashTransactionSplit>) null);
    }

    /**
     * The currency will be the one of this account.
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalance(Date, Collection)
     */
    public FixedPointNumber getBalance(final Date date, final Collection<GnucashTransactionSplit> after) {

        FixedPointNumber balance = new FixedPointNumber();

        for (Object element : getTransactionSplits()) {
            GnucashTransactionSplit split = (GnucashTransactionSplit) element;

            if (date != null && split.getTransaction().getDatePosted().after(date)) {
                if (after != null) {
                    after.add(split);
                }
                continue;
            }

            // the currency of the quantity is the one of the account
            balance.add(split.getQuantity());
        }
        return balance;
    }

    /**
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getBalance(GnucashTransactionSplit)
     */
    public FixedPointNumber getBalance(final GnucashTransactionSplit lastIncludesSplit) {

        FixedPointNumber balance = new FixedPointNumber();
        for (Object element : getTransactionSplits()) {
            GnucashTransactionSplit split = (GnucashTransactionSplit) element;

            balance.add(split.getQuantity());

            if (split == lastIncludesSplit) {
                break;
            }

        }
        return balance;
    }

    /**
     *
     * @see biz.wolschon.fileformats.gnucash.GnucashAccount#getTransactionSplitByID(java.lang.String)
     */
    public GnucashTransactionSplit getTransactionSplitByID(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("null id given!");
        }

        for (Object element2 : getTransactionSplits()) {
            GnucashTransactionSplit element = (GnucashTransactionSplit) element2;
            if (id.equals(element.getId())) {
                return element;
            }

        }
        return null;
    }

    /**
     * This is an extension to ${@link #compareNamesTo(Object)}
     * that makes shure that NEVER 2 accounts with different
     * IDs compare to 0.
     *
     * Compares our name to o.toString() .<br/>
     * If both starts with some digits the resulting
     * ${@link java.lang.Integer} are compared.<br/>
     * If one starts with a number and the other does not,
     * the one starting with a number is "bigger"<br/>
     * else and if both integers are equals a normals comparison of the
     * ${@link java.lang.String} is done.     *
     * @param   o the Object to be compared.
     * @return  a negative integer, zero, or a positive integer as this object
     *      is less than, equal to, or greater than the specified object.
     *
     * @throws ClassCastException if the specified object's type prevents it
     *         from being compared to this Object.
    * @see java.lang.Comparable#compareTo(java.lang.Object)
    */
    public int compareTo(final Object o) {

        int i = compareNamesTo(o);
        if (i != 0) {
            return i;
        }

        if (o instanceof GnucashAccount) {
            GnucashAccount other = (GnucashAccount) o;
            i = other.getId().compareTo(getId());
            if (i != 0) {
                return i;
            }
        }

        return ("" + hashCode()).compareTo("" + o.hashCode());

    }

    /**
     * Compares our name to o.toString() .<br/>
     * If both starts with some digits the resulting
     * ${@link java.lang.Integer} are compared.<br/>
     * If one starts with a number and the other does not,
     * the one starting with a number is "bigger"<br/>
     * else and if both integers are equals a normals comparison of the
     * {@link java.lang.String} is done.     *
     * @param   o the Object to be compared.
     * @return  a negative integer, zero, or a positive integer as this object
     *      is less than, equal to, or greater than the specified object.
     *
     * @throws ClassCastException if the specified object's type prevents it
     *         from being compared to this Object.
     */
    public int compareNamesTo(final Object o) throws ClassCastException {

        // usually compare the qualified name
        String other = o.toString();
        String me = getQualifiedName();

        // if we have the same parent,
        // compare the unqualified name.
        // This enshures that the exception
        // for numbers is used within our parent-
        // account too and not just in the top-
        // level accounts
        if (o instanceof GnucashAccount && ((GnucashAccount) o).getParentAccountId() != null
                && getParentAccountId() != null
                && ((GnucashAccount) o).getParentAccountId().equalsIgnoreCase(getParentAccountId())) {
            other = ((GnucashAccount) o).getName();
            me = getName();
        }

        // compare

        Long i0 = startsWithNumber(other);
        Long i1 = startsWithNumber(me);
        if (i0 == null && i1 != null) {
            return 1;
        }
        if (i1 == null && i0 != null) {
            return -1;
        }
        if (i0 == null) {
            return me.compareTo(other);
        }
        if (i1 == null) {
            return me.compareTo(other);
        }

        if (i1.equals(i0)) {
            return me.compareTo(other);
        }

        return i1.compareTo(i0);
    }

    /**
     * Helper used in ${@link #compareTo(Object)} to
     * compare names starting with a number.
     * @param s the name
     * @return the Integer build from the digits the name starts with or null
     */
    private Long startsWithNumber(final String s) {
        int digitCount = 0;
        for (int i = 0; i < s.length() && Character.isDigit(s.charAt(i)); i++) {
            digitCount++;
        }
        if (digitCount == 0) {
            return null;
        }
        return new Long(s.substring(0, digitCount));
    }

    //  ------------------------ support for propertyChangeListeners ------------------

    /**
     * support for firing PropertyChangeEvents.
     * (gets initialized only if we really have listeners)
     */
    private volatile PropertyChangeSupport myPropertyChange = null;

    /**
     * Returned value may be null if we never had listeners.
     * @return Our support for firing PropertyChangeEvents
     */
    protected PropertyChangeSupport getPropertyChangeSupport() {
        return myPropertyChange;
    }

    /**
     * Add a PropertyChangeListener to the listener list.
     * The listener is registered for all properties.
     *
     * @param listener  The PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener(final PropertyChangeListener listener) {
        if (myPropertyChange == null) {
            myPropertyChange = new PropertyChangeSupport(this);
        }
        myPropertyChange.addPropertyChangeListener(listener);
    }

    /**
     * Add a PropertyChangeListener for a specific property.  The listener
     * will be invoked only when a call on firePropertyChange names that
     * specific property.
     *
     * @param propertyName  The name of the property to listen on.
     * @param listener  The PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener) {
        if (myPropertyChange == null) {
            myPropertyChange = new PropertyChangeSupport(this);
        }
        myPropertyChange.addPropertyChangeListener(propertyName, listener);
    }

    /**
     * Remove a PropertyChangeListener for a specific property.
     *
     * @param propertyName  The name of the property that was listened on.
     * @param listener  The PropertyChangeListener to be removed
     */
    public final void removePropertyChangeListener(final String propertyName,
            final PropertyChangeListener listener) {
        if (myPropertyChange != null) {
            myPropertyChange.removePropertyChangeListener(propertyName, listener);
        }
    }

    /**
     * Remove a PropertyChangeListener from the listener list.
     * This removes a PropertyChangeListener that was registered
     * for all properties.
     *
     * @param listener  The PropertyChangeListener to be removed
     */
    public synchronized void removePropertyChangeListener(final PropertyChangeListener listener) {
        if (myPropertyChange != null) {
            myPropertyChange.removePropertyChangeListener(listener);
        }
    }

    //  -------------------------------------------------------

}