View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.moneywise.quicken.file;
18  
19  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
20  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
21  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCategoryBase;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit.MoneyWiseDepositList;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityPrice;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityPrice.MoneyWiseSecurityPriceList;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransTag;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction.MoneyWiseTransactionList;
35  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysis;
36  import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQIFPreference.MoneyWiseQIFPreferenceKey;
37  import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQIFPreference.MoneyWiseQIFPreferences;
38  import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQIFType;
39  
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.Iterator;
43  import java.util.List;
44  import java.util.Map;
45  
46  /**
47   * QIF File representation.
48   */
49  public class MoneyWiseQIFFile {
50      /**
51       * Hash multiplier.
52       */
53      protected static final int HASH_BASE = 37;
54  
55      /**
56       * Holding suffix.
57       */
58      protected static final String HOLDING_SUFFIX = "Holding";
59  
60      /**
61       * Type of file.
62       */
63      private final MoneyWiseQIFType theFileType;
64  
65      /**
66       * Start event Date.
67       */
68      private OceanusDate theStartDate;
69  
70      /**
71       * Last event Date.
72       */
73      private OceanusDate theLastDate;
74  
75      /**
76       * Map of Accounts with Events.
77       */
78      private final Map<String, MoneyWiseQIFAccountEvents> theAccountMap;
79  
80      /**
81       * Sorted List of Accounts with Events.
82       */
83      private final List<MoneyWiseQIFAccountEvents> theAccounts;
84  
85      /**
86       * Map of Payees.
87       */
88      private final Map<String, MoneyWiseQIFPayee> thePayeeMap;
89  
90      /**
91       * Sorted List of Payees.
92       */
93      private final List<MoneyWiseQIFPayee> thePayees;
94  
95      /**
96       * Map of Securities with Prices.
97       */
98      private final Map<String, MoneyWiseQIFSecurityPrices> theSecurityMap;
99  
100     /**
101      * Sorted List of Securities with Prices.
102      */
103     private final List<MoneyWiseQIFSecurityPrices> theSecurities;
104 
105     /**
106      * Map of Symbols to Securities.
107      */
108     private final Map<String, MoneyWiseQIFSecurity> theSymbolMap;
109 
110     /**
111      * Map of Parent Categories.
112      */
113     private final Map<String, MoneyWiseQIFParentCategory> theParentMap;
114 
115     /**
116      * Sorted List of Parent Categories.
117      */
118     private final List<MoneyWiseQIFParentCategory> theParentCategories;
119 
120     /**
121      * Map of Categories.
122      */
123     private final Map<String, MoneyWiseQIFEventCategory> theCategories;
124 
125     /**
126      * Map of Classes.
127      */
128     private final Map<String, MoneyWiseQIFClass> theClassMap;
129 
130     /**
131      * Sorted List of Classes.
132      */
133     private final List<MoneyWiseQIFClass> theClasses;
134 
135     /**
136      * Constructor.
137      *
138      * @param pType the file type
139      */
140     public MoneyWiseQIFFile(final MoneyWiseQIFType pType) {
141         /* Store file type */
142         theFileType = pType;
143 
144         /* Allocate maps */
145         theAccountMap = new HashMap<>();
146         thePayeeMap = new HashMap<>();
147         theSecurityMap = new HashMap<>();
148         theSymbolMap = new HashMap<>();
149         theParentMap = new HashMap<>();
150         theCategories = new HashMap<>();
151         theClassMap = new HashMap<>();
152 
153         /* Allocate maps */
154         theAccounts = new ArrayList<>();
155         thePayees = new ArrayList<>();
156         theSecurities = new ArrayList<>();
157         theParentCategories = new ArrayList<>();
158         theClasses = new ArrayList<>();
159     }
160 
161     /**
162      * Obtain the file type.
163      *
164      * @return the file type
165      */
166     public MoneyWiseQIFType getFileType() {
167         return theFileType;
168     }
169 
170     /**
171      * Does the file have classes?
172      *
173      * @return true/false
174      */
175     protected boolean hasClasses() {
176         return !theClasses.isEmpty();
177     }
178 
179     /**
180      * Obtain the number of class.
181      *
182      * @return the number
183      */
184     protected int numClasses() {
185         return theClasses.size();
186     }
187 
188     /**
189      * Obtain the classes iterator.
190      *
191      * @return the iterator
192      */
193     protected Iterator<MoneyWiseQIFClass> classIterator() {
194         return theClasses.iterator();
195     }
196 
197     /**
198      * Obtain the number of categories.
199      *
200      * @return the number
201      */
202     protected int numCategories() {
203         return theCategories.size();
204     }
205 
206     /**
207      * Obtain the category iterator.
208      *
209      * @return the iterator
210      */
211     protected Iterator<MoneyWiseQIFParentCategory> categoryIterator() {
212         return theParentCategories.iterator();
213     }
214 
215     /**
216      * Obtain the number of accounts.
217      *
218      * @return the number
219      */
220     protected int numAccounts() {
221         return theAccounts.size();
222     }
223 
224     /**
225      * Obtain the account iterator.
226      *
227      * @return the iterator
228      */
229     protected Iterator<MoneyWiseQIFAccountEvents> accountIterator() {
230         return theAccounts.iterator();
231     }
232 
233     /**
234      * Does the file have securities?
235      *
236      * @return true/false
237      */
238     protected boolean hasSecurities() {
239         return !theSecurities.isEmpty();
240     }
241 
242     /**
243      * Obtain the number of securities.
244      *
245      * @return the number
246      */
247     protected int numSecurities() {
248         return theSecurities.size();
249     }
250 
251     /**
252      * Obtain the account iterator.
253      *
254      * @return the iterator
255      */
256     protected Iterator<MoneyWiseQIFSecurityPrices> securityIterator() {
257         return theSecurities.iterator();
258     }
259 
260     /**
261      * Sort the lists.
262      */
263     protected void sortLists() {
264         /* Sort the classes */
265         theClasses.sort(null);
266 
267         /* Sort the payees */
268         thePayees.sort(null);
269 
270         /* Sort the categories */
271         theParentCategories.sort(null);
272         final Iterator<MoneyWiseQIFParentCategory> myCatIterator = categoryIterator();
273         while (myCatIterator.hasNext()) {
274             final MoneyWiseQIFParentCategory myParent = myCatIterator.next();
275 
276             /* Sort the children */
277             myParent.sortChildren();
278         }
279 
280         /* Sort the securities */
281         theSecurities.sort(null);
282         final Iterator<MoneyWiseQIFSecurityPrices> mySecIterator = securityIterator();
283         while (mySecIterator.hasNext()) {
284             final MoneyWiseQIFSecurityPrices mySecurity = mySecIterator.next();
285 
286             /* Sort the prices */
287             mySecurity.sortPrices();
288         }
289 
290         /* Sort the accounts */
291         theAccounts.sort(null);
292         final Iterator<MoneyWiseQIFAccountEvents> myAccIterator = accountIterator();
293         while (myAccIterator.hasNext()) {
294             final MoneyWiseQIFAccountEvents myAccount = myAccIterator.next();
295 
296             /* Sort the events */
297             myAccount.sortEvents();
298         }
299     }
300 
301     /**
302      * Build QIF File from data.
303      *
304      * @param pData        the data
305      * @param pAnalysis    the analysis
306      * @param pPreferences the preferences
307      * @return the QIF File
308      */
309     public static MoneyWiseQIFFile buildQIFFile(final MoneyWiseDataSet pData,
310                                                 final MoneyWiseAnalysis pAnalysis,
311                                                 final MoneyWiseQIFPreferences pPreferences) {
312         /* Access preference details */
313         final MoneyWiseQIFType myType = pPreferences.getEnumValue(MoneyWiseQIFPreferenceKey.QIFTYPE, MoneyWiseQIFType.class);
314         final OceanusDate myLastDate = pPreferences.getDateValue(MoneyWiseQIFPreferenceKey.LASTEVENT);
315 
316         /* Create new QIF File */
317         final MoneyWiseQIFFile myFile = new MoneyWiseQIFFile(myType);
318 
319         /* Build the data for the accounts */
320         myFile.buildData(pData, pAnalysis, myLastDate);
321         myFile.sortLists();
322 
323         /* Return the QIF File */
324         return myFile;
325     }
326 
327     /**
328      * Register class.
329      *
330      * @param pClass the class
331      * @return the QIFClass representation
332      */
333     public MoneyWiseQIFClass registerClass(final MoneyWiseTransTag pClass) {
334         /* Locate an existing class */
335         final String myName = pClass.getName();
336         return theClassMap.computeIfAbsent(myName, n -> {
337             final MoneyWiseQIFClass myClass = new MoneyWiseQIFClass(this, pClass);
338             theClasses.add(myClass);
339             return myClass;
340         });
341     }
342 
343     /**
344      * Register class.
345      *
346      * @param pClass the class
347      */
348     public void registerClass(final MoneyWiseQIFClass pClass) {
349         /* Locate an existing class */
350         final String myName = pClass.getName();
351         theClassMap.computeIfAbsent(myName, n -> {
352             theClasses.add(pClass);
353             return pClass;
354         });
355     }
356 
357     /**
358      * Register category.
359      *
360      * @param pCategory the category
361      * @return the QIFEventCategory representation
362      */
363     public MoneyWiseQIFEventCategory registerCategory(final MoneyWiseTransCategory pCategory) {
364         /* Locate an existing category */
365         final String myName = pCategory.getName();
366         return theCategories.computeIfAbsent(myName, n -> {
367             final MoneyWiseQIFEventCategory myCat = new MoneyWiseQIFEventCategory(this, pCategory);
368             registerCategoryToParent(pCategory.getParentCategory(), myCat);
369             return myCat;
370         });
371     }
372 
373     /**
374      * Register parent category.
375      *
376      * @param pParent   the parent category
377      * @param pCategory the QIFEventCategory to register
378      */
379     private void registerCategoryToParent(final MoneyWiseTransCategory pParent,
380                                           final MoneyWiseQIFEventCategory pCategory) {
381         /* Locate an existing parent category */
382         final String myName = pParent.getName();
383         final MoneyWiseQIFParentCategory myParent = theParentMap.computeIfAbsent(myName, n -> {
384             final MoneyWiseQIFParentCategory myParCat = new MoneyWiseQIFParentCategory(this, pParent);
385             theParentCategories.add(myParCat);
386             return myParCat;
387         });
388 
389         /* Register the category */
390         myParent.registerChild(pCategory);
391     }
392 
393     /**
394      * Register category.
395      *
396      * @param pCategory the category
397      */
398     public void registerCategory(final MoneyWiseQIFEventCategory pCategory) {
399         /* Locate an existing category */
400         final String myName = pCategory.getName();
401         final MoneyWiseQIFEventCategory myCat = theCategories.get(myName);
402         if (myCat == null) {
403             /* Locate parent separator */
404             final int myPos = myName.indexOf(MoneyWiseCategoryBase.STR_SEP);
405 
406             /* If this is a parent category */
407             if (myPos < 0) {
408                 /* Create the new Parent Category */
409                 final MoneyWiseQIFParentCategory myParent = new MoneyWiseQIFParentCategory(pCategory);
410                 theParentMap.put(myName, myParent);
411                 theParentCategories.add(myParent);
412 
413                 /* else this is a standard category */
414             } else {
415                 /* Register the new category */
416                 theCategories.put(myName, pCategory);
417 
418                 /* Determine parent name */
419                 final String myParentName = myName.substring(0, myPos);
420 
421                 /* Locate an existing parent category */
422                 final MoneyWiseQIFParentCategory myParent = theParentMap.get(myParentName);
423 
424                 /* Register against parent */
425                 myParent.registerChild(pCategory);
426             }
427         }
428     }
429 
430     /**
431      * Register account.
432      *
433      * @param pAccount the account
434      * @return the QIFAccount representation
435      */
436     public MoneyWiseQIFAccountEvents registerAccount(final MoneyWiseTransAsset pAccount) {
437         /* Locate an existing account */
438         final String myName = pAccount.getName();
439         return theAccountMap.computeIfAbsent(myName, n -> {
440             final MoneyWiseQIFAccountEvents myAccount = new MoneyWiseQIFAccountEvents(this, pAccount);
441             theAccounts.add(myAccount);
442             return myAccount;
443         });
444     }
445 
446     /**
447      * Register holding account.
448      *
449      * @param pPortfolio the portfolio
450      * @return the QIFAccount representation
451      */
452     public MoneyWiseQIFAccountEvents registerHoldingAccount(final MoneyWisePortfolio pPortfolio) {
453         /* Locate an existing account */
454         final String myName = pPortfolio.getName() + HOLDING_SUFFIX;
455         return theAccountMap.computeIfAbsent(myName, n -> {
456             final MoneyWiseQIFAccountEvents myAccount = new MoneyWiseQIFAccountEvents(this, myName);
457             theAccounts.add(myAccount);
458             return myAccount;
459         });
460     }
461 
462     /**
463      * Register account.
464      *
465      * @param pAccount the account
466      * @return the QIFAccount representation
467      */
468     public MoneyWiseQIFAccountEvents registerAccount(final MoneyWiseQIFAccount pAccount) {
469         /* Locate an existing account */
470         final String myName = pAccount.getName();
471         return theAccountMap.computeIfAbsent(myName, n -> {
472             final MoneyWiseQIFAccountEvents myAccount = new MoneyWiseQIFAccountEvents(pAccount);
473             theAccounts.add(myAccount);
474             return myAccount;
475         });
476     }
477 
478     /**
479      * Register payee.
480      *
481      * @param pPayee the payee
482      * @return the QIFPayee representation
483      */
484     public MoneyWiseQIFPayee registerPayee(final MoneyWisePayee pPayee) {
485         /* Locate an existing payee */
486         final String myName = pPayee.getName();
487         return thePayeeMap.computeIfAbsent(myName, n -> {
488             final MoneyWiseQIFPayee myPayee = new MoneyWiseQIFPayee(pPayee);
489             thePayees.add(myPayee);
490             return myPayee;
491         });
492     }
493 
494     /**
495      * Register payee.
496      *
497      * @param pPayee the payee
498      * @return the QIFPayee representation
499      */
500     public MoneyWiseQIFPayee registerPayee(final String pPayee) {
501         /* Locate an existing payee */
502         return thePayeeMap.computeIfAbsent(pPayee, n -> {
503             final MoneyWiseQIFPayee myPayee = new MoneyWiseQIFPayee(pPayee);
504             thePayees.add(myPayee);
505             return myPayee;
506         });
507     }
508 
509     /**
510      * Register security.
511      *
512      * @param pSecurity the security
513      * @return the QIFSecurity representation
514      */
515     public MoneyWiseQIFSecurity registerSecurity(final MoneyWiseSecurity pSecurity) {
516         /* Locate an existing security */
517         final String myName = pSecurity.getName();
518         final MoneyWiseQIFSecurityPrices mySecurity = theSecurityMap.computeIfAbsent(myName, n -> {
519             final MoneyWiseQIFSecurityPrices mySec = new MoneyWiseQIFSecurityPrices(this, pSecurity);
520             theSymbolMap.put(pSecurity.getSymbol(), mySec.getSecurity());
521             theSecurities.add(mySec);
522             return mySec;
523         });
524 
525         /* Return the security */
526         return mySecurity.getSecurity();
527     }
528 
529     /**
530      * Register security.
531      *
532      * @param pSecurity the security
533      */
534     public void registerSecurity(final MoneyWiseQIFSecurity pSecurity) {
535         /* Locate an existing security */
536         final String myName = pSecurity.getName();
537         theSecurityMap.computeIfAbsent(myName, n -> {
538             final MoneyWiseQIFSecurityPrices mySecurity = new MoneyWiseQIFSecurityPrices(this, pSecurity);
539             theSymbolMap.put(pSecurity.getSymbol(), mySecurity.getSecurity());
540             theSecurities.add(mySecurity);
541             return mySecurity;
542         });
543     }
544 
545     /**
546      * Register price.
547      *
548      * @param pPrice the price
549      */
550     public void registerPrice(final MoneyWiseSecurityPrice pPrice) {
551         /* Locate an existing security price list */
552         final MoneyWiseSecurity mySecurity = pPrice.getSecurity();
553         final MoneyWiseQIFSecurityPrices mySecurityList = theSecurityMap.get(mySecurity.getName());
554         if (mySecurityList != null) {
555             /* Add price to the list */
556             mySecurityList.addPrice(pPrice);
557         }
558     }
559 
560     /**
561      * Register price.
562      *
563      * @param pPrice the price
564      */
565     public void registerPrice(final MoneyWiseQIFPrice pPrice) {
566         /* Locate an existing security price list */
567         final MoneyWiseQIFSecurity mySecurity = pPrice.getSecurity();
568         final MoneyWiseQIFSecurityPrices mySecurityList = theSecurityMap.get(mySecurity.getName());
569         if (mySecurityList != null) {
570             /* Loop through the prices */
571             final Iterator<MoneyWiseQIFPrice> myIterator = pPrice.priceIterator();
572             while (myIterator.hasNext()) {
573                 final MoneyWiseQIFPrice myPrice = myIterator.next();
574 
575                 /* Add price to the list */
576                 mySecurityList.addPrice(myPrice);
577             }
578         }
579     }
580 
581     /**
582      * Obtain category.
583      *
584      * @param pName the name of the category
585      * @return the category
586      */
587     protected MoneyWiseQIFEventCategory getCategory(final String pName) {
588         /* Lookup the category */
589         return theCategories.get(pName);
590     }
591 
592     /**
593      * Obtain account.
594      *
595      * @param pName the name of the account
596      * @return the account
597      */
598     protected MoneyWiseQIFAccount getAccount(final String pName) {
599         /* Lookup the security */
600         final MoneyWiseQIFAccountEvents myAccount = getAccountEvents(pName);
601         return (myAccount == null)
602                 ? null
603                 : myAccount.getAccount();
604     }
605 
606     /**
607      * Obtain account events.
608      *
609      * @param pName the name of the account
610      * @return the account
611      */
612     protected MoneyWiseQIFAccountEvents getAccountEvents(final String pName) {
613         /* Lookup the account */
614         return theAccountMap.get(pName);
615     }
616 
617     /**
618      * Obtain security.
619      *
620      * @param pName the name of the security
621      * @return the security
622      */
623     protected MoneyWiseQIFSecurity getSecurity(final String pName) {
624         /* Lookup the security */
625         final MoneyWiseQIFSecurityPrices myList = getSecurityPrices(pName);
626         return myList == null
627                 ? null
628                 : myList.getSecurity();
629     }
630 
631     /**
632      * Obtain security by Symbol.
633      *
634      * @param pSymbol the symbol of the security
635      * @return the security
636      */
637     protected MoneyWiseQIFSecurity getSecurityBySymbol(final String pSymbol) {
638         /* Lookup the security */
639         return theSymbolMap.get(pSymbol);
640     }
641 
642     /**
643      * Obtain security prices.
644      *
645      * @param pName the name of the security
646      * @return the security
647      */
648     protected MoneyWiseQIFSecurityPrices getSecurityPrices(final String pName) {
649         /* Lookup the security */
650         return theSecurityMap.get(pName);
651     }
652 
653     /**
654      * Obtain class.
655      *
656      * @param pName the name of the class
657      * @return the class
658      */
659     protected MoneyWiseQIFClass getClass(final String pName) {
660         /* Lookup the class */
661         return theClassMap.get(pName);
662     }
663 
664     /**
665      * Build data.
666      *
667      * @param pData     the data
668      * @param pAnalysis the analysis
669      * @param pLastDate the last date
670      */
671     public void buildData(final MoneyWiseDataSet pData,
672                           final MoneyWiseAnalysis pAnalysis,
673                           final OceanusDate pLastDate) {
674         /* Create a builder */
675         final MoneyWiseQIFBuilder myBuilder = new MoneyWiseQIFBuilder(this, pData, pAnalysis);
676 
677         /* Store dates */
678         theStartDate = pData.getDateRange().getStart();
679         theLastDate = pLastDate;
680 
681         /* Build opening balances */
682         buildOpeningBalances(myBuilder, pData.getDeposits());
683 
684         /* Loop through the events */
685         final MoneyWiseTransactionList myEvents = pData.getTransactions();
686         final Iterator<MoneyWiseTransaction> myIterator = myEvents.iterator();
687         while (myIterator.hasNext()) {
688             final MoneyWiseTransaction myEvent = myIterator.next();
689 
690             /* Break loop if the event is too late */
691             final OceanusDate myDate = myEvent.getDate();
692             if (myDate.compareTo(pLastDate) > 0) {
693                 break;
694             }
695 
696             /* Process the event */
697             myBuilder.processEvent(myEvent);
698         }
699 
700         /* Build prices for securities */
701         buildPrices(pData.getSecurityPrices());
702     }
703 
704     /**
705      * Build opening balances.
706      *
707      * @param pBuilder     the builder
708      * @param pDepositList the deposit list
709      */
710     private void buildOpeningBalances(final MoneyWiseQIFBuilder pBuilder,
711                                       final MoneyWiseDepositList pDepositList) {
712         /* Loop through the prices */
713         final Iterator<MoneyWiseDeposit> myIterator = pDepositList.iterator();
714         while (myIterator.hasNext()) {
715             final MoneyWiseDeposit myDeposit = myIterator.next();
716 
717             /* Ignore if no opening balance */
718             final OceanusMoney myBalance = myDeposit.getOpeningBalance();
719             if (myBalance == null) {
720                 continue;
721             }
722 
723             /* Process the balance */
724             pBuilder.processBalance(myDeposit, theStartDate, myBalance);
725         }
726     }
727 
728     /**
729      * Build prices.
730      *
731      * @param pPriceList the price list
732      */
733     private void buildPrices(final MoneyWiseSecurityPriceList pPriceList) {
734         /* Loop through the prices */
735         final Iterator<MoneyWiseSecurityPrice> myIterator = pPriceList.iterator();
736         while (myIterator.hasNext()) {
737             final MoneyWiseSecurityPrice myPrice = myIterator.next();
738 
739             /* Break loop if the price is too late */
740             final OceanusDate myDate = myPrice.getDate();
741             if (myDate.compareTo(theLastDate) > 0) {
742                 break;
743             }
744 
745             /* Register the price */
746             registerPrice(myPrice);
747         }
748     }
749 
750     @Override
751     public boolean equals(final Object pThat) {
752         /* Handle trivial cases */
753         if (this == pThat) {
754             return true;
755         }
756         if (pThat == null) {
757             return false;
758         }
759 
760         /* Check class */
761         if (!(pThat instanceof MoneyWiseQIFFile)) {
762             return false;
763         }
764 
765         /* Cast correctly */
766         final MoneyWiseQIFFile myThat = (MoneyWiseQIFFile) pThat;
767 
768         /* Check file type */
769         if (!theFileType.equals(myThat.theFileType)) {
770             return false;
771         }
772 
773         /* Check class list */
774         if (!theClasses.equals(myThat.theClasses)) {
775             return false;
776         }
777 
778         /* Check parent categories */
779         if (!theParentCategories.equals(myThat.theParentCategories)) {
780             return false;
781         }
782 
783         /* Check securities list */
784         if (!theSecurities.equals(myThat.theSecurities)) {
785             return false;
786         }
787 
788         /* Check payees list */
789         if (!thePayees.equals(myThat.thePayees)) {
790             return false;
791         }
792 
793         /* Check accounts */
794         return theAccounts.equals(myThat.theAccounts);
795     }
796 
797     @Override
798     public int hashCode() {
799         int myResult = MoneyWiseQIFFile.HASH_BASE * theFileType.hashCode();
800         myResult += theClasses.hashCode();
801         myResult *= MoneyWiseQIFFile.HASH_BASE;
802         myResult += theParentCategories.hashCode();
803         myResult *= MoneyWiseQIFFile.HASH_BASE;
804         myResult += theSecurities.hashCode();
805         myResult *= MoneyWiseQIFFile.HASH_BASE;
806         myResult += thePayees.hashCode();
807         myResult *= MoneyWiseQIFFile.HASH_BASE;
808         myResult += theAccounts.hashCode();
809         return myResult;
810     }
811 }