MoneyWiseQIFWriter.java

/*
 * MoneyWise: Finance Application
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.moneywise.quicken.file;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;

import java.util.Iterator;

/**
 * Writer class for QIF Files.
 */
public class MoneyWiseQIFWriter {
    /**
     * Quicken Date Format.
     */
    protected static final String QIF_DATEFORMAT = "dd/MM/yy";

    /**
     * Quicken BaseYear.
     */
    protected static final int QIF_BASEYEAR = 1970;

    /**
     * AutoSwitch option.
     */
    protected static final String QIF_AUTOSWITCH = "AutoSwitch";

    /**
     * Item type.
     */
    protected static final String QIF_ITEM = "!Account";

    /**
     * Number of stages.
     */
    protected static final int NUM_STAGES = 6;

    /**
     * Thread Status.
     */
    private final TethysUIThreadStatusReport theReport;

    /**
     * QIF File.
     */
    private final MoneyWiseQIFFile theFile;

    /**
     * Data formatter.
     */
    private final OceanusDataFormatter theFormatter;

    /**
     * Constructor.
     *
     * @param pFactory the factory
     * @param pReport  the report
     * @param pFile    the QIF file.
     */
    public MoneyWiseQIFWriter(final TethysUIFactory<?> pFactory,
                              final TethysUIThreadStatusReport pReport,
                              final MoneyWiseQIFFile pFile) {
        /* Store parameters */
        theReport = pReport;
        theFile = pFile;

        /* Allocate the formatter and set date format */
        theFormatter = pFactory.newDataFormatter();
        theFormatter.setFormat(QIF_DATEFORMAT);
    }

    /**
     * Write to Stream.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    public void writeFile(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Declare the stages */
        theReport.setNumStages(NUM_STAGES);

        /* Write the classes */
        writeClasses(pStream);

        /* Write the categories */
        writeCategories(pStream);

        /* Write the accounts */
        writeAccounts(pStream);

        /* Write the securities */
        writeSecurities(pStream);

        /* Write the events */
        writeEvents(pStream);

        /* Write the prices */
        writePrices(pStream);
    }

    /**
     * Write Classes.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    private void writeClasses(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();

        /* Update status bar */
        theReport.setNewStage("Writing classes");
        theReport.setNumSteps(theFile.numClasses());

        /* Skip stage if we have no classes */
        if (!theFile.hasClasses()) {
            return;
        }

        /* Format Item Type header */
        MoneyWiseQIFRecord.formatItemType(MoneyWiseQIFClass.QIF_ITEM, myBuilder);

        /* Write Class header */
        pStream.write(myBuilder.toString());
        myBuilder.setLength(0);

        /* Loop through the categories */
        final Iterator<MoneyWiseQIFClass> myIterator = theFile.classIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseQIFClass myClass = myIterator.next();

            /* Format the record */
            myClass.formatRecord(theFormatter, myBuilder);

            /* Write Category record */
            pStream.write(myBuilder.toString());
            myBuilder.setLength(0);

            /* Report the progress */
            theReport.setNextStep();
        }
    }

    /**
     * Write Categories.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    private void writeCategories(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();

        /* Update status bar */
        theReport.setNewStage("Writing categories");
        theReport.setNumSteps(theFile.numCategories());

        /* Format Item Type header */
        MoneyWiseQIFRecord.formatItemType(MoneyWiseQIFEventCategory.QIF_ITEM, myBuilder);

        /* Write Category header */
        pStream.write(myBuilder.toString());
        myBuilder.setLength(0);

        /* Loop through the parent categories */
        final Iterator<MoneyWiseQIFParentCategory> myIterator = theFile.categoryIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseQIFParentCategory myCategory = myIterator.next();

            /* Format the record */
            myCategory.formatRecord(theFormatter, myBuilder);

            /* Write Category records */
            pStream.write(myBuilder.toString());
            myBuilder.setLength(0);

            /* Report the progress */
            theReport.setStepsDone(myCategory.numChildren());
        }
    }

    /**
     * Write Accounts.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    private void writeAccounts(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();

        /* Update status bar */
        theReport.setNewStage("Writing accounts");
        theReport.setNumSteps(theFile.numAccounts());

        /* Set AutoSwitch and header */
        MoneyWiseQIFRecord.setSwitch(QIF_AUTOSWITCH, myBuilder);
        MoneyWiseQIFRecord.formatHeader(MoneyWiseQIFAccount.QIF_HDR, myBuilder);

        /* Write Accounts header */
        pStream.write(myBuilder.toString());
        myBuilder.setLength(0);

        /* Loop through the accounts */
        final Iterator<MoneyWiseQIFAccountEvents> myIterator = theFile.accountIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseQIFAccountEvents myEvents = myIterator.next();
            final MoneyWiseQIFAccount myAccount = myEvents.getAccount();

            /* Format the record */
            myAccount.formatRecord(theFormatter, myBuilder);

            /* Write Account record */
            pStream.write(myBuilder.toString());
            myBuilder.setLength(0);

            /* Report the progress */
            theReport.setNextStep();
        }
    }

    /**
     * Write Securities.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    private void writeSecurities(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();

        /* Update status bar */
        theReport.setNewStage("Writing securities");
        theReport.setNumSteps(theFile.numSecurities());

        /* Skip step if we have no securities */
        if (!theFile.hasSecurities()) {
            return;
        }

        /* Clear AutoSwitch */
        MoneyWiseQIFRecord.clearSwitch(QIF_AUTOSWITCH, myBuilder);

        /* Write Securities header */
        pStream.write(myBuilder.toString());
        myBuilder.setLength(0);

        /* Loop through the securities */
        final Iterator<MoneyWiseQIFSecurityPrices> myIterator = theFile.securityIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseQIFSecurityPrices myList = myIterator.next();
            final MoneyWiseQIFSecurity mySecurity = myList.getSecurity();

            /* Format Item Type header */
            MoneyWiseQIFRecord.formatItemType(MoneyWiseQIFSecurity.QIF_ITEM, myBuilder);

            /* Format the record */
            mySecurity.formatRecord(theFormatter, myBuilder);

            /* Write Security record */
            pStream.write(myBuilder.toString());
            myBuilder.setLength(0);

            /* Report the progress */
            theReport.setNextStep();
        }

        /* Set AutoSwitch */
        MoneyWiseQIFRecord.setSwitch(QIF_AUTOSWITCH, myBuilder);

        /* Write Securities header */
        pStream.write(myBuilder.toString());
        myBuilder.setLength(0);
    }

    /**
     * Write Prices.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    private void writeEvents(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();

        /* Update status bar */
        theReport.setNewStage("Writing account events");
        theReport.setNumSteps(theFile.numAccounts());

        /* Loop through the accounts */
        final Iterator<MoneyWiseQIFAccountEvents> myIterator = theFile.accountIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseQIFAccountEvents myEvents = myIterator.next();
            final MoneyWiseQIFAccount myAccount = myEvents.getAccount();

            /* Format Item Type header */
            MoneyWiseQIFRecord.formatHeader(MoneyWiseQIFAccount.QIF_HDR, myBuilder);

            /* Format the record */
            myAccount.formatRecord(theFormatter, myBuilder);

            /* Format Item Type */
            MoneyWiseQIFRecord.formatItemType(myAccount.getType(), myBuilder);

            /* Write Account record */
            pStream.write(myBuilder.toString());
            myBuilder.setLength(0);

            /* Loop through the events */
            final Iterator<MoneyWiseQIFEventRecord<?>> myEvtIterator = myEvents.eventIterator();
            while (myEvtIterator.hasNext()) {
                final MoneyWiseQIFEventRecord<?> myEvent = myEvtIterator.next();

                /* Format the record */
                myEvent.formatRecord(theFormatter, myBuilder);

                /* Write Event record */
                pStream.write(myBuilder.toString());
                myBuilder.setLength(0);
            }

            /* Report the progress */
            theReport.setNextStep();
        }
    }

    /**
     * Write Prices.
     *
     * @param pStream the output stream
     * @throws OceanusException on error
     */
    private void writePrices(final MoneyWiseQIFStreamWriter pStream) throws OceanusException {
        /* Create string builder */
        final StringBuilder myBuilder = new StringBuilder();

        /* Update status bar */
        theReport.setNewStage("Writing prices");
        theReport.setNumSteps(theFile.numSecurities());

        /* Skip step if we have no prices */
        if (!theFile.hasSecurities()) {
            return;
        }

        /* Loop through the prices */
        final Iterator<MoneyWiseQIFSecurityPrices> myIterator = theFile.securityIterator();
        while (myIterator.hasNext()) {
            final MoneyWiseQIFSecurityPrices myPriceList = myIterator.next();

            /* Format Prices */
            myPriceList.formatPrices(theFormatter, myBuilder);

            /* Write Price record */
            pStream.write(myBuilder.toString());
            myBuilder.setLength(0);

            /* Report the progress */
            theReport.setNextStep();
        }
    }
}