MoneyWiseQIFParser.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.moneywise.quicken.definitions.MoneyWiseQIFType;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Class to parse a QIFFile.
 */
public class MoneyWiseQIFParser {
    /**
     * The QIFFile being built.
     */
    private final MoneyWiseQIFFile theFile;

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

    /**
     * Record mode.
     */
    private MoneyWiseQIFSection theSection;

    /**
     * Active account.
     */
    private MoneyWiseQIFAccountEvents theActive;

    /**
     * is active account portfolio?
     */
    private boolean isPortfolio;

    /**
     * Constructor.
     *
     * @param pFactory  the gui factory
     * @param pFileType the QIF file type.
     */
    public MoneyWiseQIFParser(final TethysUIFactory<?> pFactory,
                              final MoneyWiseQIFType pFileType) {
        /* Create new file */
        theFile = new MoneyWiseQIFFile(pFileType);

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

    /**
     * Obtain file.
     *
     * @return the QIF file.
     */
    public MoneyWiseQIFFile getFile() {
        return theFile;
    }

    /**
     * Load from Stream.
     *
     * @param pStream the input stream
     * @return continue true/false
     * @throws IOException on error
     */
    public boolean loadFile(final BufferedReader pStream) throws IOException {
        /* List of lines */
        final List<String> myLines = new ArrayList<>();

        /* Loop through the file */
        while (true) {
            /* Read the next line and break on EOF */
            final String myLine = pStream.readLine();
            if (myLine == null) {
                break;
            }

            /* If this is a command */
            if (myLine.startsWith(MoneyWiseQIFRecord.QIF_CMD)) {
                /* Process the mode */
                processMode(myLine);
                /* If this is an EOI */
            } else if (myLine.equals(MoneyWiseQIFRecord.QIF_EOI)) {
                /* Process the records */
                processRecord(myLines);

                /* else normal line */
            } else {
                /* Ignore blank lines */
                if (myLine.length() > 0) {
                    myLines.add(myLine);
                }
            }
        }

        /* Sort the file lists */
        theFile.sortLists();

        /* Return to the caller */
        return true;
    }

    /**
     * Process the mode.
     *
     * @param pLine the line to process
     */
    private void processMode(final String pLine) {
        /* If the line is a type record */
        if (pLine.startsWith(MoneyWiseQIFRecord.QIF_ITEMTYPE)) {
            /* Access the type */
            final String myType = pLine.substring(MoneyWiseQIFRecord.QIF_ITEMTYPE.length());

            /* Determine which section this describes */
            final MoneyWiseQIFSection mySection = MoneyWiseQIFSection.determineType(myType);

            /* If we found a section */
            if (mySection != null) {
                /* Switch on the section */
                switch (mySection) {
                    case CLASS:
                    case CATEGORY:
                    case SECURITY:
                    case PRICE:
                        theSection = mySection;
                        theActive = null;
                        break;
                    default:
                        theSection = null;
                        break;
                }

                /* else if we have an active account */
            } else if (theActive != null) {
                /* Make sure that the type matches */
                final String myActiveType = theActive.getAccount().getType();
                if (myActiveType.equals(myType)) {
                    /* Set events */
                    theSection = MoneyWiseQIFSection.EVENT;

                    /* Note portfolio */
                    isPortfolio = myType.equals(MoneyWiseQIFAccount.QIFACT_INVST);

                    /* Not recognised */
                } else {
                    theSection = null;
                }

                /* Not recognised */
            } else {
                theSection = null;
            }

            /* Handle Account call */
        } else if (pLine.startsWith(MoneyWiseQIFAccount.QIF_HDR)) {
            /* Look for account record */
            theSection = MoneyWiseQIFSection.ACCOUNT;
        }
    }

    /**
     * Process the record.
     *
     * @param pLines the lines to process
     */
    private void processRecord(final List<String> pLines) {
        /* Switch on the section */
        switch (theSection) {
            case CLASS:
                processClassRecord(pLines);
                break;
            case CATEGORY:
                processCategoryRecord(pLines);
                break;
            case ACCOUNT:
                processAccountRecord(pLines);
                break;
            case SECURITY:
                processSecurityRecord(pLines);
                break;
            case EVENT:
                processEventRecord(pLines);
                break;
            case PRICE:
                processPriceRecord(pLines);
                break;
            default:
                break;
        }

        /* Clear line list */
        pLines.clear();
    }

    /**
     * Process the class record.
     *
     * @param pLines the lines to process
     */
    private void processClassRecord(final List<String> pLines) {
        /* register the class */
        final MoneyWiseQIFClass myClass = new MoneyWiseQIFClass(theFile, pLines);
        theFile.registerClass(myClass);
    }

    /**
     * Process the category record.
     *
     * @param pLines the lines to process
     */
    private void processCategoryRecord(final List<String> pLines) {
        /* Register the category */
        final MoneyWiseQIFEventCategory myCategory = new MoneyWiseQIFEventCategory(theFile, pLines);
        theFile.registerCategory(myCategory);
    }

    /**
     * Process the account record.
     *
     * @param pLines the lines to process
     */
    private void processAccountRecord(final List<String> pLines) {
        /* Register the account */
        final MoneyWiseQIFAccount myAccount = new MoneyWiseQIFAccount(theFile, theFormatter, pLines);
        theActive = theFile.registerAccount(myAccount);
    }

    /**
     * Process the security record.
     *
     * @param pLines the lines to process
     */
    private void processSecurityRecord(final List<String> pLines) {
        /* Register the security */
        final MoneyWiseQIFSecurity mySecurity = new MoneyWiseQIFSecurity(theFile, pLines);
        theFile.registerSecurity(mySecurity);
    }

    /**
     * Process the event record.
     *
     * @param pLines the lines to process
     */
    private void processEventRecord(final List<String> pLines) {
        /* Switch on portfolio */
        if (isPortfolio) {
            /* Register the event */
            final MoneyWiseQIFPortfolioEvent myEvent = new MoneyWiseQIFPortfolioEvent(theFile, theFormatter, pLines);
            theActive.addEvent(myEvent);
        } else {
            /* Register the event */
            final MoneyWiseQIFEvent myEvent = new MoneyWiseQIFEvent(theFile, theFormatter, pLines);
            theActive.addEvent(myEvent);
        }
    }

    /**
     * Process the price record.
     *
     * @param pLines the lines to process
     */
    private void processPriceRecord(final List<String> pLines) {
        /* Register the price */
        final MoneyWiseQIFPrice myPrice = new MoneyWiseQIFPrice(theFile, theFormatter, pLines);
        theFile.registerPrice(myPrice);
    }

    /**
     * QIF File section.
     */
    private enum MoneyWiseQIFSection {
        /**
         * Classes.
         */
        CLASS("Class"),

        /**
         * Category.
         */
        CATEGORY("Cat"),

        /**
         * Account.
         */
        ACCOUNT("Account"),

        /**
         * Security.
         */
        SECURITY("Security"),

        /**
         * EVENT.
         */
        EVENT(null),

        /**
         * Price.
         */
        PRICE("Prices");

        /**
         * Type.
         */
        private final String theType;

        /**
         * Constructor.
         *
         * @param pType the type
         */
        MoneyWiseQIFSection(final String pType) {
            theType = pType;
        }

        /**
         * Determine section.
         *
         * @param pLine the line to check
         * @return the section
         */
        private static MoneyWiseQIFSection determineType(final String pLine) {
            /* Loop through the values */
            for (MoneyWiseQIFSection mySection : MoneyWiseQIFSection.values()) {
                /* If we have a match */
                if (pLine.equals(mySection.theType)) {
                    return mySection;
                }
            }

            /* Not found */
            return null;
        }
    }
}