gg.imports.GrisbiFile050.java Source code

Java tutorial

Introduction

Here is the source code for gg.imports.GrisbiFile050.java

Source

/*
 * GrisbiFile050.java
 *
 * Copyright (C) 2009 Francois Duchemin
 *
 * This file is part of GrisbiGraphs.
 *
 * GrisbiGraphs 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 2 of the License, or
 * (at your option) any later version.
 *
 * GrisbiGraphs 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 GrisbiGraphs; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package gg.imports;

import gg.db.datamodel.DateFormatException;
import gg.db.entities.GrisbiCategory;
import gg.db.datamodel.Datamodel;
import gg.db.entities.Account;
import gg.db.entities.Category;
import gg.db.entities.Currency;
import gg.db.entities.Payee;
import gg.db.entities.Transaction;
import gg.db.datamodel.Period;
import gg.utilities.Utilities;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.Node;
import org.joda.time.LocalDate;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;

/**
 * <B>GrisbiFile050</B>
 * <UL>
 * <LI>Permits to import Grisbi 0.5.x files</LI>
 * <LI>The public method <CODE>importFile()</CODE> imports the XML document into the embedded database</LI>
 * </UL>
 * @author Francois Duchemin
 */
public class GrisbiFile050 implements Importer {

    /** Document (supports XPath), which contains the XML Grisbi document to import */
    private Document grisbiFileDocument;
    /** Path to the Grisbi file to import*/
    private String pathToGrisbiFile;
    /** Is the import task cancelled? */
    private boolean importCancelled;
    /** Current work unit (for the progress bar) */
    private int workUnit;
    /** Logger */
    private Logger log = Logger.getLogger(GrisbiFile050.class.getName());

    /**
     * Creates a new instance of GrisbiFile050
     * @param grisbiFileDocument Document which contains the Grisbi file to import into the embedded database
     * @param pathToGrisbiFile Path to the Grisbi file to import
     */
    public GrisbiFile050(Document grisbiFileDocument, String pathToGrisbiFile) {
        if (grisbiFileDocument == null) {
            throw new IllegalArgumentException("The parameter 'grisbiFileDocument' is null");
        }
        if (pathToGrisbiFile == null) {
            throw new IllegalArgumentException("The parameter 'pathToGrisbiFile' is null");
        }
        this.grisbiFileDocument = grisbiFileDocument;
        this.pathToGrisbiFile = pathToGrisbiFile;
        this.importCancelled = false;
    }

    /**
     * Gets expected number of payees
     * @return Expected number of payees
     * @throws ParsingException Expected number of payees not found
     * @throws NumberFormatException The expected number of payees is not numeric
     */
    private int getExpectedNumberOfPayees() throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "getExpectedNumberOfPayees");
        Node expectedNumberOfPayeesNode = grisbiFileDocument.selectSingleNode("/Grisbi/Tiers/Generalites/Nb_tiers");
        if (expectedNumberOfPayeesNode == null) {
            throw new ParsingException("The expected number of payees has not been found in the Grisbi file '"
                    + pathToGrisbiFile + "'");
        }
        String expectedNumberOfPayeesValue = expectedNumberOfPayeesNode.getStringValue();
        assert (expectedNumberOfPayeesValue != null);
        int expectedNumberOfPayees = Integer.parseInt(expectedNumberOfPayeesValue);

        log.exiting(this.getClass().getName(), "getExpectedNumberOfPayees", expectedNumberOfPayees);
        return expectedNumberOfPayees;
    }

    /**
     * Gets expected number of categories
     * @return Expected number of categories
     * @throws ParsingException Expected number of categories not found
     * @throws NumberFormatException The expected number of categories is not numeric
     */
    private int getExpectedNumberOfCategories() throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "getExpectedNumberOfCategories");
        Node expectedNumberOfCategoriesNode = grisbiFileDocument
                .selectSingleNode("/Grisbi/Categories/Generalites/Nb_categories");
        if (expectedNumberOfCategoriesNode == null) {
            throw new ParsingException("The expected number of categories has not been found in the Grisbi file '"
                    + pathToGrisbiFile + "'");
        }
        String expectedNumberOfCategoriesValue = expectedNumberOfCategoriesNode.getStringValue();
        assert (expectedNumberOfCategoriesValue != null);
        int expectedNumberOfCategories = Integer.parseInt(expectedNumberOfCategoriesValue);

        log.exiting(this.getClass().getName(), "getExpectedNumberOfCategories", expectedNumberOfCategories);
        return expectedNumberOfCategories;
    }

    /**
     * Gets expected number of currencies
     * @return Expected number of currencies
     * @throws ParsingException Expected number of currencies not found
     * @throws NumberFormatException The expected number of currencies is not numeric
     */
    private int getExpectedNumberOfCurrencies() throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "getExpectedNumberOfCurrencies");
        Node expectedNumberOfCurrenciesNode = grisbiFileDocument
                .selectSingleNode("/Grisbi/Devises/Generalites/Nb_devises");
        if (expectedNumberOfCurrenciesNode == null) {
            throw new ParsingException("The expected number of currencies has not been found in the Grisbi file '"
                    + pathToGrisbiFile + "'");
        }
        String expectedNumberOfCurrenciesValue = expectedNumberOfCurrenciesNode.getStringValue();
        assert (expectedNumberOfCurrenciesValue != null);
        int expectedNumberOfCurrencies = Integer.parseInt(expectedNumberOfCurrenciesValue);

        log.exiting(this.getClass().getName(), "getExpectedNumberOfCurrencies", expectedNumberOfCurrencies);
        return expectedNumberOfCurrencies;
    }

    /**
     * Gets expected number of transactions (for all accounts)
     * @return Expected number of transactions
     * @throws ParsingException Expected number of transactions not found
     * @throws NumberFormatException The expected number of transactions is not numeric
     */
    private int getExpectedNumberOfTransactions() throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "getExpectedNumberOfTransactions");
        int expectedNumberOfTransactions = 0;

        List listOfAccounts = grisbiFileDocument.selectNodes("/Grisbi/Comptes/Compte");
        for (Object accountObject : listOfAccounts) {
            Node accountNode = (Node) accountObject;
            assert (accountNode != null);

            // Get the name of the current account
            Node accountNameNode = accountNode.selectSingleNode("Details/Nom");
            assert (accountNameNode != null);
            String accountName = accountNameNode.getStringValue();
            assert (accountName != null);

            // Get the expected number of transactions of the account
            Node expectedNumberOfTransactionsNode = accountNode.selectSingleNode("Details/Nb_operations");
            if (expectedNumberOfTransactionsNode == null) {
                throw new ParsingException(
                        "The expected number of transactions has not been found for the account '" + accountName
                                + "' in the Grisbi file '" + pathToGrisbiFile + "'");
            }
            String expectedNumberOfTransactionsValue = expectedNumberOfTransactionsNode.getStringValue();
            assert (expectedNumberOfTransactionsValue != null);
            int expectedNumberOfTransactionsForCurrentAccount = Integer.parseInt(expectedNumberOfTransactionsValue);

            expectedNumberOfTransactions += expectedNumberOfTransactionsForCurrentAccount;
        }

        log.exiting(this.getClass().getName(), "getExpectedNumberOfTransactions", expectedNumberOfTransactions);
        return expectedNumberOfTransactions;
    }

    /**
     * Imports the payees into the embedded database
     * @param p Progress handle for the progress bar
     * @param expectedNumberOfPayees Expected number of payees: the number of imported payees should be equal to this number
     * @throws ParsingException
     * <UL>
     * <LI>If there is a problem finding the needed nodes</LI>
     * <LI>If the number of imported payees is not equal to the number of payees defined in the Grisbi file</LI>
     * </UL>
     * @throws NumberFormatException If a string is read when a number is expected
     */
    private void importPayees(ProgressHandle p, int expectedNumberOfPayees)
            throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "importPayees");
        long startImportingPayeesTime = System.currentTimeMillis();

        // Save default payee in the database
        Datamodel.savePayee(Payee.NO_PAYEE); // This constant is used to search the transactions for which no payee is defined

        // Import the payees from the Grisbi file into the embedded database
        int numberOfImportedPayees = 0;
        List listOfPayees = grisbiFileDocument.selectNodes("/Grisbi/Tiers/Detail_des_tiers/Tiers");
        Iterator payeesIterator = listOfPayees.iterator();
        while (payeesIterator.hasNext() && !isImportCancelled()) { // Go through the list of payees found in the Grisbi XML file
            // Get the current payee node
            Node payeeNode = (Node) payeesIterator.next();

            // Get the ID and the name of the current payee
            String payeeIdValue = payeeNode.valueOf("@No");
            assert (payeeIdValue != null);
            long payeeId = Long.parseLong(payeeIdValue);
            assert (payeeId > 0);
            String payeeName = payeeNode.valueOf("@Nom");
            assert (payeeName != null);

            // Create a new payee and save it in the embedded database
            Payee payee = new Payee(payeeId, payeeName, false);
            Datamodel.savePayee(payee);

            numberOfImportedPayees++;
            p.progress(workUnit++);
        }

        // Make sure that all payees have been imported
        if (!isImportCancelled() && numberOfImportedPayees != expectedNumberOfPayees) {
            throw new ParsingException("The number of imported payees (" + numberOfImportedPayees
                    + ") is not equal to the expected number of payees (" + expectedNumberOfPayees
                    + ") written in the Grisbi file '" + pathToGrisbiFile + "'");
        }

        long endImportingPayeesTime = System.currentTimeMillis();
        log.info(numberOfImportedPayees + " payees have been successfully imported in "
                + (endImportingPayeesTime - startImportingPayeesTime) + " ms");
        log.exiting(this.getClass().getName(), "importPayees");
    }

    /**
     * Imports the categories into the embedded database
     * @param p Progress handle for the progress bar
     * @param expectedNumberOfCategories Expected number of categories: the number of imported categories should be equal to this number
     * @throws ParsingException
     * <UL>
     * <LI>If there is a problem finding the needed nodes</LI>
     * <LI>If the number of imported categories is not equal to the number of categories defined in the Grisbi file</LI>
     * </UL>
     * @throws NumberFormatException If a string is read when a number is expected
     */
    private void importCategories(ProgressHandle p, int expectedNumberOfCategories)
            throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "importCategories");
        long startImportingCategoriesTime = System.currentTimeMillis();

        // Save system categories in the embedded database
        Datamodel.saveCategory(Category.TRANSFER);
        Datamodel.saveCategory(Category.BREAKDOWN_OF_TRANSACTIONS);
        Datamodel.saveCategory(Category.NO_CATEGORY);

        // Import the categories from the Grisbi file into the embedded database
        int numberOfImportedCategories = 0;
        List listOfCategories = grisbiFileDocument
                .selectNodes("/Grisbi/Categories/Detail_des_categories/Categorie");
        Iterator categoriesIterator = listOfCategories.iterator();
        while (categoriesIterator.hasNext() && !isImportCancelled()) { // Go through the list of categories written in the Grisbi file
            // Get the current category node
            Node categoryNode = (Node) categoriesIterator.next();

            // Get the ID and the name of the current category
            String categoryIdValue = categoryNode.valueOf("@No");
            assert (categoryIdValue != null);
            long categoryId = Long.parseLong(categoryIdValue); // Get the ID of the category
            assert (categoryId > 0);
            String categoryName = categoryNode.valueOf("@Nom"); // Get the name of the category
            assert (categoryName != null);

            // Create a new category and save it in the embedded database
            Category category = new Category(categoryId, 0L, categoryName, null, false); // There is no sub-category - the field "grisbi_sub_category_id" is set to 0
            Datamodel.saveCategory(category);

            // Import the sub-categories into the database
            List listOfSubCategories = categoryNode.selectNodes("Sous-categorie");
            Iterator subCategoriesIterator = listOfSubCategories.iterator();
            while (subCategoriesIterator.hasNext() && !isImportCancelled()) { // Go through the list of sub-categories of the current category
                // Get the current sub-category node
                Node subCategoryNode = (Node) subCategoriesIterator.next();

                // Get the ID and the name of the current sub-category
                String subCategoryIdValue = subCategoryNode.valueOf("@No");
                assert (subCategoryIdValue != null);
                long subCategoryId = Long.parseLong(subCategoryIdValue); // Get the ID of the sub-category
                assert (subCategoryId > 0);
                String subCategoryName = subCategoryNode.valueOf("@Nom"); // Get the name of the sub-category
                assert (subCategoryName != null);

                // Create a new sub-category and save it in the embedded database
                Category subCategory = new Category(categoryId, subCategoryId, subCategoryName, category, false);
                Datamodel.saveCategory(subCategory);
            }

            // For each category, save an empty sub-category
            Category noSubCategory = new Category(categoryId, Category.NO_SUB_CATEGORY_ID, "No sub-category",
                    category, false);
            Datamodel.saveCategory(noSubCategory);

            numberOfImportedCategories++;
            p.progress(workUnit++);
        }

        // Make sure that all categories have been imported
        if (!isImportCancelled() && numberOfImportedCategories != expectedNumberOfCategories) {
            throw new ParsingException("The number of imported categories (" + numberOfImportedCategories
                    + ") is not equal to the expected number of categories (" + expectedNumberOfCategories
                    + ") written in the Grisbi file '" + pathToGrisbiFile + "'");
        }

        long endImportingCategoriesTime = System.currentTimeMillis();
        log.info(numberOfImportedCategories + " categories have been successfully imported in "
                + (endImportingCategoriesTime - startImportingCategoriesTime) + " ms");
        log.exiting(this.getClass().getName(), "importCategories");
    }

    /**
     * Imports the currencies into the database
     * @param p Progress handle for the progress bar
     * @param expectedNumberOfCurrencies Expected number of currencies: the number of imported currencies should be equal to this number
     * @throws ParsingException
     * <UL>
     * <LI>If there is a problem finding the needed nodes</LI>
     * <LI>If the number of imported currencies is not equal to the number of currencies defined in the Grisbi file</LI>
     * </UL>
     * @throws NumberFormatException If a string is read when a number is expected
     */
    private void importCurrencies(ProgressHandle p, int expectedNumberOfCurrencies)
            throws ParsingException, NumberFormatException {
        log.entering(this.getClass().getName(), "importCurrencies");
        long startImportingCurrenciesTime = System.currentTimeMillis();

        // Import the currencies into the embedded database
        int numberOfImportedCurrencies = 0;
        List listOfCurrencies = grisbiFileDocument.selectNodes("/Grisbi/Devises/Detail_des_devises/Devise");
        Iterator currenciesIterator = listOfCurrencies.iterator();
        while (currenciesIterator.hasNext() && !isImportCancelled()) { // Go through the list of currencies
            // Get the current currency node
            Node currencyNode = (Node) currenciesIterator.next();

            // Get the ID, the name and the ISO code of the current currency
            String currencyIdValue = currencyNode.valueOf("@No");
            assert (currencyIdValue != null);
            long currencyId = Long.parseLong(currencyIdValue); // Get the ID of the currency
            assert (currencyId > 0);
            String currencyName = currencyNode.valueOf("@Nom"); // Get the name of the currency
            assert (currencyName != null);
            String currencyCode = currencyNode.valueOf("@Code"); // Get the code of the currency
            assert (currencyCode != null);
            String currencyIsoCode = currencyNode.valueOf("@IsoCode"); // Get the code ISO of the currency
            assert (currencyIsoCode != null);
            String currencyExchangeRateValue = currencyNode.valueOf("@Change"); // Get the exchange rate of the currency
            assert (currencyExchangeRateValue != null);
            BigDecimal currencyExchangeRate = new BigDecimal(currencyExchangeRateValue.replace(',', '.'));
            String currencyRelation = currencyNode.valueOf("@Rapport_entre_devises");
            assert (currencyRelation != null);
            boolean currencyMultiply = (currencyRelation.compareTo("1") == 0);
            String euroConversionValue = currencyNode.valueOf("@Passage_euro");
            assert (euroConversionValue != null);
            boolean currencyEuroConversion = (euroConversionValue.compareTo("1") == 0);

            // Create a new currency and save the currency in the database
            // By default the currencies are not active
            // When the accounts are imported, the currencies are activated
            Currency currency = new Currency(currencyId, currencyName, currencyCode, currencyIsoCode,
                    new BigDecimal(0), new BigDecimal(0), currencyExchangeRate, currencyMultiply,
                    currencyEuroConversion, false);
            Datamodel.saveCurrency(currency);

            numberOfImportedCurrencies++;
            p.progress(workUnit++);
        }

        // Make sure that all currencies have been imported
        if (!isImportCancelled() && numberOfImportedCurrencies != expectedNumberOfCurrencies) {
            throw new ParsingException("The number of imported currencies (" + numberOfImportedCurrencies
                    + ") is not equal to the expected number of currencies (" + expectedNumberOfCurrencies
                    + ") in the Grisbi file '" + pathToGrisbiFile + "'");
        }

        long endImportingCurrenciesTime = System.currentTimeMillis();
        log.info(numberOfImportedCurrencies + " currencies have been successfully imported in "
                + (endImportingCurrenciesTime - startImportingCurrenciesTime) + " ms");
        log.exiting(this.getClass().getName(), "importCurrencies");
    }

    /**
     * Imports the accounts into the database<BR/>
     * The method <CODE>importCurrencies()</CODE> has to be called before
     * @throws ParsingException If there is a problem finding the needed nodes
     * @throws NumberFormatException If a string is read when a number is expected
     * @throws DateFormatException If the format of the date of the first transaction is invalid
     */
    private void importAccounts() throws ParsingException, NumberFormatException, DateFormatException {
        log.entering(this.getClass().getName(), "importAccounts");
        long startImportingAccountsTime = System.currentTimeMillis();

        // Get all the currencies already saved in the database (the method importCurrencies() has to be called before)
        Map<Long, Currency> currencies = Datamodel.getCurrenciesWithId();

        // Import the accounts in the embedded database
        long numberOfImportedAccounts = 0;
        List listOfAccounts = grisbiFileDocument.selectNodes("/Grisbi/Comptes/Compte");
        Iterator accountsIterator = listOfAccounts.iterator();
        while (accountsIterator.hasNext() && !isImportCancelled()) {

            // Get the account node
            Node accountNode = (Node) accountsIterator.next();

            // Get the "Active" property of the account: is the account closed?
            Node accountActiveNode = accountNode.selectSingleNode("Details/Compte_cloture");
            assert (accountActiveNode != null);
            String accountActiveValue = accountActiveNode.getStringValue();
            assert (accountActiveValue != null);
            boolean accountActive = (accountActiveValue.compareTo("1") != 0);

            // Get the ID of the account
            Node accountIdNode = accountNode.selectSingleNode("Details/No_de_compte");
            assert (accountIdNode != null);
            String accountIdValue = accountIdNode.getStringValue();
            assert (accountIdValue != null);
            long accountId = Long.parseLong(accountIdValue);
            assert (accountId >= 0);

            // Get the name of the account
            Node accountNameNode = accountNode.selectSingleNode("Details/Nom");
            assert (accountNameNode != null);
            String accountName = accountNameNode.getStringValue();
            assert (accountName != null);

            // Get the currency of the account
            Node currencyIdNode = accountNode.selectSingleNode("Details/Devise");
            assert (currencyIdNode != null);
            String currencyIdValue = currencyIdNode.getStringValue();
            assert (currencyIdValue != null);
            long currencyId = Long.parseLong(currencyIdValue);
            assert (currencyId > 0);

            // Get the corresponding Currency object
            Currency accountCurrency = currencies.get(currencyId); // An account has always a currency in Grisbi
            assert (accountCurrency != null);

            // Activate the currency if needed
            if (accountActive && !accountCurrency.getActive()) {
                accountCurrency.setActive(true);
            }

            // Get the initial amount of the account
            Node accountInitialAmountNode = accountNode.selectSingleNode("Details/Solde_initial");
            assert (accountInitialAmountNode != null);
            String accountInitialAmountValue = accountInitialAmountNode.getStringValue();
            assert (accountInitialAmountValue != null);
            BigDecimal accountInitialAmount = new BigDecimal(accountInitialAmountValue.replace(',', '.'));
            accountInitialAmount = accountInitialAmount.setScale(2, RoundingMode.HALF_EVEN);

            // Get the current balance of the account
            Node accountBalanceNode = accountNode.selectSingleNode("Details/Solde_courant");
            assert (accountBalanceNode != null);
            String accountBalanceValue = accountBalanceNode.getStringValue();
            assert (accountBalanceValue != null);
            BigDecimal accountBalance = new BigDecimal(accountBalanceValue.replace(',', '.'));
            accountBalance = accountBalance.setScale(2, RoundingMode.HALF_EVEN);

            // Create a new account and save the account in the embedded database
            Account account = new Account(accountId, accountName, accountCurrency, accountInitialAmount,
                    accountBalance, accountActive);
            Datamodel.saveAccount(account);

            // Update the currency's balance and the currency's initial amount
            if (accountActive) {
                accountCurrency.setBalance(accountCurrency.getBalance().add(accountBalance));
                accountCurrency.setInitialAmount(accountCurrency.getInitialAmount().add(accountInitialAmount));
            }

            numberOfImportedAccounts++;
        }

        // Update the currencies (active flag, balance, initial amount)
        for (Currency currency : currencies.values()) {
            Datamodel.saveCurrency(currency);
        }

        long endImportingAccountsTime = System.currentTimeMillis();
        log.info(numberOfImportedAccounts + " accounts have been successfully imported in "
                + (endImportingAccountsTime - startImportingAccountsTime) + " ms");
        log.exiting(this.getClass().getName(), "importAccounts");
    }

    /**
     * Imports the transactions into the embedded database<BR/>
     * The methods <CODE>importCurrencies()</CODE>, <CODE>importPayees()</CODE>,
     * <CODE>importAccounts()</CODE>, and <CODE>importCategories()</CODE> have to be called before
     * @param p Progress handle for the progress bar
     * @throws ParsingException If there is a problem finding the needed nodes
     * @throws NumberFormatException If a string is read when a number is expected
     * @throws DateFormatException If the date format of a transaction is invalid
     */
    private void importTransactions(ProgressHandle p)
            throws ParsingException, NumberFormatException, DateFormatException {
        log.entering(this.getClass().getName(), "importTransactions");
        long startImportingTotalTransactionsTime = System.currentTimeMillis();

        Map<Long, Account> accounts = Datamodel.getAccountsWithId();
        Map<Long, Payee> payees = Datamodel.getPayeesWithId();
        Map<GrisbiCategory, Category> categories = Datamodel.getCategoriesWithGrisbiCategory();
        Map<Long, Currency> currencies = Datamodel.getCurrenciesWithId();

        // Import the transactions from each account into the embedded database
        long totalNumberOfImportedTransactions = 0;
        List listOfAccounts = grisbiFileDocument.selectNodes("/Grisbi/Comptes/Compte");
        Iterator accountsIterator = listOfAccounts.iterator();
        while (accountsIterator.hasNext() && !isImportCancelled()) { // Go through the accounts
            long startImportingTransactionsTime = System.currentTimeMillis();

            // Get the account's node
            Node accountNode = (Node) accountsIterator.next();
            assert (accountNode != null);

            // Get the ID of the account
            Node accountIdNode = accountNode.selectSingleNode("Details/No_de_compte");
            assert (accountIdNode != null);
            String accountIdValue = accountIdNode.getStringValue();
            assert (accountIdValue != null);
            long accountId = Long.parseLong(accountIdValue);
            assert (accountId >= 0);

            // Get the name of the current account
            Node accountNameNode = accountNode.selectSingleNode("Details/Nom");
            assert (accountNameNode != null);
            String accountName = accountNameNode.getStringValue();
            assert (accountName != null);

            // Display on the progress bar the account from which transactions are imported
            p.progress("Importing transactions from " + accountName);

            // Get the corresponding Account object
            Account account = accounts.get(accountId);
            assert (account != null);

            // Get the expected number of transactions of the account
            Node expectedNumberOfTransactionsNode = accountNode.selectSingleNode("Details/Nb_operations");
            if (expectedNumberOfTransactionsNode == null) {
                throw new ParsingException(
                        "The expected number of transactions has not been found for the account '" + accountName
                                + "' in the Grisbi file '" + pathToGrisbiFile + "'");
            }
            String expectedNumberOfTransactionsValue = expectedNumberOfTransactionsNode.getStringValue();
            assert (expectedNumberOfTransactionsValue != null);
            int expectedNumberOfTransactions = Integer.parseInt(expectedNumberOfTransactionsValue);

            // Import the transactions of the account into the embedded database
            int numberOfImportedTransactions = 0;
            Map<Long, Transaction> transactions = new HashMap<Long, Transaction>(); // Map containing all the saved transactions - the key of the map is the transaction's ID
            List listOfTransactions = accountNode.selectNodes("Detail_des_operations/Operation");
            Iterator transactionsIterator = listOfTransactions.iterator();
            while (transactionsIterator.hasNext() && !isImportCancelled()) { // Go through the list of transactions of the account

                // Get the transaction node
                Node transactionNode = (Node) transactionsIterator.next();

                // Get the ID of the transaction
                assert (transactionNode != null);
                String transactionIdValue = transactionNode.valueOf("@No");
                assert (transactionIdValue != null);
                long transactionId = Long.parseLong(transactionIdValue);
                assert (transactionId > 0);

                // Get the date of the transaction
                String transactionDateValue = transactionNode.valueOf("@D");
                assert (transactionDateValue != null && transactionDateValue.compareTo("") != 0);
                // Get the date from the string
                LocalDate transactionDate = Period.getDate(transactionDateValue); // Throws a DateFormatException if the format of 'transactionDateValue' is not valid
                assert (transactionDate != null);

                // Get the exchange rate
                String exchangeRateValue = transactionNode.valueOf("@Tc");
                assert (exchangeRateValue != null);
                BigDecimal exchangeRate = new BigDecimal(exchangeRateValue.replace(',', '.')); // exchangeRate = 0 if there is no echange rate

                // Get the fees
                String feesValue = transactionNode.valueOf("@Fc");
                assert (feesValue != null);
                BigDecimal fees = new BigDecimal(feesValue.replace(',', '.'));

                // Get the amount of the transaction
                String transactionAmountValue = transactionNode.valueOf("@M");
                assert (transactionAmountValue != null);
                BigDecimal transactionAmount = new BigDecimal(transactionAmountValue.replace(',', '.'));

                // Get the currency of the transaction
                String transactionCurrencyIdValue = transactionNode.valueOf("@De");
                assert (transactionCurrencyIdValue != null);
                long transactionCurrencyId = Long.parseLong(transactionCurrencyIdValue); // Get the ID of the currency
                Currency transactionCurrency = currencies.get(transactionCurrencyId);
                assert (transactionCurrency != null);
                Currency accountCurrency = currencies.get(account.getCurrency().getId());
                assert (accountCurrency != null);

                // Update the amount of the transaction if the currency of the transaction is different from the currency of the account
                // (Method found in the Grisbi source file: devises.c/calcule_montant_devise_renvoi)
                if (transactionCurrency.getId().compareTo(accountCurrency.getId()) != 0) {
                    if (accountCurrency.getEuroConversion()) {
                        if (transactionCurrency.getName().compareToIgnoreCase("euro") == 0) {
                            transactionAmount = transactionAmount.multiply(accountCurrency.getExchangeRate());
                        }
                    } else if (accountCurrency.getEuroConversion()
                            && transactionCurrency.getName().compareToIgnoreCase("euro") != 0) {
                        transactionAmount = transactionAmount.divide(transactionCurrency.getExchangeRate(),
                                RoundingMode.HALF_EVEN);
                    } else {
                        if (exchangeRate.compareTo(BigDecimal.ZERO) != 0) {
                            String transactionRdcValue = transactionNode.valueOf("@Rdc");
                            assert (transactionRdcValue != null);
                            long transactionRdc = Long.parseLong(transactionRdcValue);

                            if (transactionRdc == 1) {
                                transactionAmount = (transactionAmount.divide(exchangeRate, RoundingMode.HALF_EVEN))
                                        .subtract(fees);
                            } else {
                                transactionAmount = (transactionAmount.multiply(exchangeRate)).subtract(fees);
                            }
                        } else if (transactionCurrency.getExchangeRate().compareTo(BigDecimal.ZERO) != 0) {
                            if (transactionCurrency.getMultiply()) {
                                transactionAmount = (transactionAmount
                                        .multiply(transactionCurrency.getExchangeRate())).subtract(fees);
                            } else {
                                transactionAmount = (transactionAmount.divide(transactionCurrency.getExchangeRate(),
                                        RoundingMode.HALF_EVEN)).subtract(fees);
                            }
                        } else {
                            transactionAmount = new BigDecimal(0);
                        }
                    }
                }
                transactionAmount = transactionAmount.setScale(2, RoundingMode.HALF_EVEN);

                // If the current transaction is a transfer:
                String twinTransaction = transactionNode.valueOf("@Ro"); // Twin transaction
                assert (twinTransaction != null);
                String accountOfTransfer = transactionNode.valueOf("@Rc"); // Account where the amount has been transfered (account of the twin transaction)
                assert (accountOfTransfer != null);

                // Is the current transaction a breakdown of transactions?
                String breakdownOfTransaction = transactionNode.valueOf("@Ov"); // breakdownOfTransaction = 1 if the current trasnsaction is a breakdown of transactions
                assert (breakdownOfTransaction != null);

                // Get the category and the sub-category of the transaction
                String transactionGrisbiCategoryIdValue = transactionNode.valueOf("@C"); // Get the category ID ('0' means that the category is not defined)
                assert (transactionGrisbiCategoryIdValue != null);
                long transactionGrisbiCategoryId = Long.parseLong(transactionGrisbiCategoryIdValue);
                assert (transactionGrisbiCategoryId >= 0);

                String transactionGrisbiSubCategoryIdValue = transactionNode.valueOf("@Sc"); // Get the sub-category ID ('0' means that the sub-category is not defined)
                assert (transactionGrisbiSubCategoryIdValue != null);
                long transactionGrisbiSubCategoryId = Long.parseLong(transactionGrisbiSubCategoryIdValue);
                assert (transactionGrisbiSubCategoryId >= 0);

                // Check if the category is "Transfer" or "Breakdown of transactions"
                Category transactionCategory = null;
                GrisbiCategory transactionGrisbiCategory = new GrisbiCategory(0, 0);
                if (transactionGrisbiCategoryId == 0
                        && (twinTransaction.compareTo("0") != 0 || accountOfTransfer.compareTo("0") != 0)) {
                    // The current transaction is a transfer
                    transactionCategory = Category.TRANSFER;
                } else if (transactionGrisbiCategoryId == 0 && breakdownOfTransaction.compareTo("1") == 0) {
                    // The current transaction is a breakdown of transactions
                    transactionCategory = Category.BREAKDOWN_OF_TRANSACTIONS;
                } else if (transactionGrisbiSubCategoryId != 0) {
                    transactionGrisbiCategory.setCategoryId(transactionGrisbiCategoryId);
                    transactionGrisbiCategory.setSubCategoryId(transactionGrisbiSubCategoryId);
                    transactionCategory = categories.get(transactionGrisbiCategory);
                    assert (transactionCategory != null);
                } else if (transactionGrisbiCategoryId != 0) {
                    // Else, if a category is defined, get the category
                    transactionGrisbiCategory.setCategoryId(transactionGrisbiCategoryId);
                    transactionGrisbiCategory.setSubCategoryId(Category.NO_SUB_CATEGORY_ID);
                    transactionCategory = categories.get(transactionGrisbiCategory);
                    assert (transactionCategory != null);
                } else {
                    // Else, no category is defined
                    transactionCategory = Category.NO_CATEGORY;
                }
                assert (transactionCategory != null);

                // Get the comment of the transaction
                String transactionComment = transactionNode.valueOf("@N");
                assert (transactionComment != null);

                // Get the payee of the transaction
                String transactionPayeeIdValue = transactionNode.valueOf("@T"); // 'transactionPayeeIdValue' contains '0' if no payee is defined
                assert (transactionPayeeIdValue != null);
                long transactionPayeeId = Long.parseLong(transactionPayeeIdValue);
                assert (transactionPayeeId >= 0);
                Payee transactionPayee = null;
                if (transactionPayeeId != 0) { // Get the payee if it has been defined
                    transactionPayee = payees.get(transactionPayeeId);
                    assert (transactionPayee != null);
                } else { // No payee has been defined
                    transactionPayee = Payee.NO_PAYEE;
                }

                // Get the parent transaction
                String transactionParentIdValue = transactionNode.valueOf("@Va"); // '0' means that the transaction is a top-transaction ; not '0' means that the transaction is a sub-transaction
                assert (transactionParentIdValue != null);
                long transactionParentId = Long.parseLong(transactionParentIdValue);
                assert (transactionParentId >= 0);
                Transaction transactionParent = null;
                if (transactionParentId != 0) {
                    // Get the parent transaction
                    transactionParent = transactions.get(transactionParentId); // 'transactionParentId' is the ID of the parent transaction (in Grisbi files, parent transactions are always BEFORE sub-transactions)
                    assert (transactionParent != null);
                } else {
                    // The current transaction has no parent transaction
                    // The current transaction is a top transaction
                    transactionParent = null;
                }

                // Create a new transaction and save the transaction in the embedded database
                Transaction transaction = new Transaction(transactionDate, account, transactionAmount,
                        transactionCategory, transactionComment, transactionPayee, transactionParent);
                Datamodel.saveTransaction(transaction);
                transactions.put(transactionId, transaction); // Save the transaction in the map, so that parent transactions can be found

                numberOfImportedTransactions++;
                p.progress(workUnit++);
            }

            // Make sure that the number of imported transactions and sub-transactions is the expected number
            if (!isImportCancelled() && numberOfImportedTransactions != expectedNumberOfTransactions) {
                throw new ParsingException("For the account '" + accountName
                        + "', the number of imported transactions (" + numberOfImportedTransactions
                        + ") is not equal to the expected number of transactions (" + expectedNumberOfTransactions
                        + ") in the Grisbi file '" + pathToGrisbiFile + "'");
            }

            long endImportingTransactionsTime = System.currentTimeMillis();
            log.info(numberOfImportedTransactions + " transactions have been successfully imported in the account '"
                    + accountName + "' in " + (endImportingTransactionsTime - startImportingTransactionsTime)
                    + " ms");
            totalNumberOfImportedTransactions += numberOfImportedTransactions;
        }

        long endImportingTotalTransactionsTime = System.currentTimeMillis();
        log.info(totalNumberOfImportedTransactions + " transactions have been successfully imported in "
                + (endImportingTotalTransactionsTime - startImportingTotalTransactionsTime) + " ms");
        log.exiting(this.getClass().getName(), "importTransactions");
    }

    /**
     * Imports the Grisbi file into the database<BR/>
     * <UL>
     * <LI>Import the payees</LI>
     * <LI>Import the categories</LI>
     * <LI>Import the currencies</LI>
     * <LI>Import the accounts</LI>
     * <LI>Import the transactions and the sub-transactions</LI>
     * </UL>
     * @return Number of miliseconds needed to import the Grisbi file into the database
     * @throws ParsingException If there is a problem finding the needed nodes
     * @throws NumberFormatException If a string is read when a number is expected
     * @throws DateFormatException If a the format of a date is invalid
     */
    @Override
    public long importFile() throws ParsingException, NumberFormatException, DateFormatException {
        log.entering(this.getClass().getName(), "importFile");
        importCancelled = false;
        workUnit = 0;
        ProgressHandle p = ProgressHandleFactory.createHandle(
                NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.ImportingGrisbiFile"), new Cancellable() {

                    @Override
                    public boolean cancel() {
                        Utilities.changeCursorWaitStatus(false);
                        importCancelled = true;
                        log.info("Import of '" + pathToGrisbiFile + "' has been cancelled");
                        return true;
                    }
                });

        long startImportingFileTime = System.currentTimeMillis();

        // Start progress bar
        p.setInitialDelay(0);
        p.start();

        // Get number of expected entities
        int expectedNumberOfPayees = getExpectedNumberOfPayees();
        int expectedNumberOfCategories = getExpectedNumberOfCategories();
        int expectedNumberOfCurrencies = getExpectedNumberOfCurrencies();
        int expectedNumberOfTransactions = getExpectedNumberOfTransactions();
        int totalEntities = expectedNumberOfPayees + expectedNumberOfCategories + expectedNumberOfCurrencies
                + expectedNumberOfTransactions;
        log.info("Expected number of entities (payees, categories, currencies, transactions): " + totalEntities);

        p.switchToDeterminate(totalEntities);

        // Empty the embedded database
        p.progress(NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.EmptyingDatabase"));
        Datamodel.emptyDatabase();

        // Import the Grisbi file into the embedded database
        if (!isImportCancelled()) {
            p.progress(NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.ImportingPayees"));
            importPayees(p, expectedNumberOfPayees);
        }

        if (!isImportCancelled()) {
            p.progress(NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.ImportingCategories"));
            importCategories(p, expectedNumberOfCategories);
        }

        if (!isImportCancelled()) {
            p.progress(NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.ImportingCurrencies"));
            importCurrencies(p, expectedNumberOfCurrencies);
        }

        if (!isImportCancelled()) {
            p.progress(NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.ImportingAccounts"));
            importAccounts();
        }

        if (!isImportCancelled()) {
            p.progress(NbBundle.getMessage(GrisbiFile050.class, "GrisbiFile050.ImportingTransactions"));
            importTransactions(p);
        }

        p.finish();
        long endImportingFileTime = System.currentTimeMillis();
        long importDuration = endImportingFileTime - startImportingFileTime;

        log.exiting(this.getClass().getName(), "importFile", importDuration);
        return importDuration;
    }

    @Override
    public boolean isImportCancelled() {
        return importCancelled;
    }
}