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.OceanusDecimal;
21  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
22  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
23  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
24  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
25  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
26  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
27  import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQLineType;
28  
29  import java.util.ArrayList;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  /**
34   * A standard event line in the QIF file.
35   *
36   * @param <T> the line type
37   */
38  public abstract class MoneyWiseQIFLine<T extends MoneyWiseQLineType> {
39      /**
40       * Reconciled flag.
41       */
42      protected static final String QIF_RECONCILED = "X";
43  
44      /**
45       * Transfer begin char.
46       */
47      private static final String QIF_XFERSTART = "[";
48  
49      /**
50       * Transfer end char.
51       */
52      private static final String QIF_XFEREND = "]";
53  
54      /**
55       * Class indicator.
56       */
57      private static final String QIF_CLASS = "/";
58  
59      /**
60       * Class separator.
61       */
62      private static final String QIF_CLASSSEP = "-";
63  
64      /**
65       * Category separator.
66       */
67      private static final String QIF_CATSEP = "!";
68  
69      /**
70       * Obtain line type.
71       *
72       * @return the line type
73       */
74      public abstract T getLineType();
75  
76      /**
77       * Format the data.
78       *
79       * @param pFormatter the data formatter
80       * @param pBuilder   the string builder
81       */
82      protected abstract void formatData(OceanusDataFormatter pFormatter,
83                                         StringBuilder pBuilder);
84  
85      /**
86       * Format lines.
87       *
88       * @param pFormatter the data formatter
89       * @param pBuilder   the string builder
90       */
91      protected void formatLine(final OceanusDataFormatter pFormatter,
92                                final StringBuilder pBuilder) {
93          /* Add the lineType */
94          final T myType = getLineType();
95          pBuilder.append(myType.getSymbol());
96  
97          /* Format the Data */
98          formatData(pFormatter, pBuilder);
99      }
100 
101     @Override
102     public boolean equals(final Object pThat) {
103         /* Handle trivial cases */
104         if (this == pThat) {
105             return true;
106         }
107         if (pThat == null) {
108             return false;
109         }
110 
111         /* Check class */
112         if (!getClass().equals(pThat.getClass())) {
113             return false;
114         }
115 
116         /* Cast correctly */
117         final MoneyWiseQIFLine<?> myLine = (MoneyWiseQIFLine<?>) pThat;
118 
119         /* Check value */
120         return getLineType().equals(myLine.getLineType());
121     }
122 
123     @Override
124     public int hashCode() {
125         return getLineType().hashCode();
126     }
127 
128     /**
129      * The String line.
130      *
131      * @param <X> the line type
132      */
133     protected abstract static class MoneyWiseQIFStringLine<X extends MoneyWiseQLineType>
134             extends MoneyWiseQIFLine<X> {
135         /**
136          * The value.
137          */
138         private final String theValue;
139 
140         /**
141          * Constructor.
142          *
143          * @param pValue the Value
144          */
145         protected MoneyWiseQIFStringLine(final String pValue) {
146             /* Store the value */
147             theValue = pValue;
148         }
149 
150         @Override
151         public String toString() {
152             return getValue();
153         }
154 
155         /**
156          * Obtain Value.
157          *
158          * @return the value
159          */
160         protected String getValue() {
161             return theValue;
162         }
163 
164         @Override
165         protected void formatData(final OceanusDataFormatter pFormatter,
166                                   final StringBuilder pBuilder) {
167             /* Append the string data */
168             pBuilder.append(theValue);
169         }
170 
171         @Override
172         public boolean equals(final Object pThat) {
173             /* Handle trivial cases */
174             if (this == pThat) {
175                 return true;
176             }
177             if (pThat == null) {
178                 return false;
179             }
180 
181             /* Check class */
182             if (!getClass().equals(pThat.getClass())) {
183                 return false;
184             }
185 
186             /* Cast correctly */
187             final MoneyWiseQIFStringLine<?> myLine = (MoneyWiseQIFStringLine<?>) pThat;
188 
189             /* Check line type */
190             if (!getLineType().equals(myLine.getLineType())) {
191                 return false;
192             }
193 
194             /* Check value */
195             return theValue.equals(myLine.getValue());
196         }
197 
198         @Override
199         public int hashCode() {
200             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
201             return myResult + theValue.hashCode();
202         }
203     }
204 
205     /**
206      * The Money line.
207      *
208      * @param <X> the line type
209      */
210     protected abstract static class MoneyWiseQIFMoneyLine<X extends MoneyWiseQLineType>
211             extends MoneyWiseQIFLine<X> {
212         /**
213          * The money.
214          */
215         private final OceanusMoney theMoney;
216 
217         /**
218          * Constructor.
219          *
220          * @param pMoney the Money
221          */
222         protected MoneyWiseQIFMoneyLine(final OceanusMoney pMoney) {
223             /* Store data */
224             theMoney = pMoney;
225         }
226 
227         @Override
228         public String toString() {
229             return getMoney().toString();
230         }
231 
232         /**
233          * Obtain Money.
234          *
235          * @return the money
236          */
237         protected OceanusMoney getMoney() {
238             return theMoney;
239         }
240 
241         @Override
242         protected void formatData(final OceanusDataFormatter pFormatter,
243                                   final StringBuilder pBuilder) {
244             /* Convert to Decimal */
245             final OceanusDecimal myDecimal = new OceanusDecimal(theMoney);
246 
247             /* Append the string data */
248             pBuilder.append(pFormatter.formatObject(myDecimal));
249         }
250 
251         @Override
252         public boolean equals(final Object pThat) {
253             /* Handle trivial cases */
254             if (this == pThat) {
255                 return true;
256             }
257             if (pThat == null) {
258                 return false;
259             }
260 
261             /* Check class */
262             if (!getClass().equals(pThat.getClass())) {
263                 return false;
264             }
265 
266             /* Cast correctly */
267             final MoneyWiseQIFMoneyLine<?> myLine = (MoneyWiseQIFMoneyLine<?>) pThat;
268 
269             /* Check line type */
270             if (!getLineType().equals(myLine.getLineType())) {
271                 return false;
272             }
273 
274             /* Check value */
275             return theMoney.equals(myLine.getMoney());
276         }
277 
278         @Override
279         public int hashCode() {
280             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
281             return myResult + theMoney.hashCode();
282         }
283     }
284 
285     /**
286      * The Date line.
287      *
288      * @param <X> the line type
289      */
290     protected abstract static class MoneyWiseQIFDateLine<X extends MoneyWiseQLineType>
291             extends MoneyWiseQIFLine<X> {
292         /**
293          * The date.
294          */
295         private final OceanusDate theDate;
296 
297         /**
298          * Constructor.
299          *
300          * @param pDate the Date
301          */
302         protected MoneyWiseQIFDateLine(final OceanusDate pDate) {
303             /* Store the date */
304             theDate = pDate;
305         }
306 
307         @Override
308         public String toString() {
309             return getDate().toString();
310         }
311 
312         /**
313          * Obtain Date.
314          *
315          * @return the date
316          */
317         public OceanusDate getDate() {
318             return theDate;
319         }
320 
321         @Override
322         protected void formatData(final OceanusDataFormatter pFormatter,
323                                   final StringBuilder pBuilder) {
324             /* Append the string data */
325             pBuilder.append(pFormatter.formatObject(theDate));
326         }
327 
328         @Override
329         public boolean equals(final Object pThat) {
330             /* Handle trivial cases */
331             if (this == pThat) {
332                 return true;
333             }
334             if (pThat == null) {
335                 return false;
336             }
337 
338             /* Check class */
339             if (!getClass().equals(pThat.getClass())) {
340                 return false;
341             }
342 
343             /* Cast correctly */
344             final MoneyWiseQIFDateLine<?> myLine = (MoneyWiseQIFDateLine<?>) pThat;
345 
346             /* Check line type */
347             if (!getLineType().equals(myLine.getLineType())) {
348                 return false;
349             }
350 
351             /* Check value */
352             return theDate.equals(myLine.getDate());
353         }
354 
355         @Override
356         public int hashCode() {
357             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
358             return myResult + theDate.hashCode();
359         }
360     }
361 
362     /**
363      * The Flag line.
364      *
365      * @param <X> the line type
366      */
367     protected abstract static class MoneyWiseQIFFlagLine<X extends MoneyWiseQLineType>
368             extends MoneyWiseQIFLine<X> {
369         /**
370          * The flag status.
371          */
372         private final Boolean isSet;
373 
374         /**
375          * Constructor.
376          *
377          * @param pSet is the flag set?
378          */
379         protected MoneyWiseQIFFlagLine(final Boolean pSet) {
380             /* Store data */
381             isSet = pSet;
382         }
383 
384         @Override
385         public String toString() {
386             return isSet().toString();
387         }
388 
389         /**
390          * Obtain Cleared status.
391          *
392          * @return true/false
393          */
394         protected Boolean isSet() {
395             return isSet;
396         }
397 
398         @Override
399         public boolean equals(final Object pThat) {
400             /* Handle trivial cases */
401             if (this == pThat) {
402                 return true;
403             }
404             if (pThat == null) {
405                 return false;
406             }
407 
408             /* Check class */
409             if (!getClass().equals(pThat.getClass())) {
410                 return false;
411             }
412 
413             /* Cast correctly */
414             final MoneyWiseQIFFlagLine<?> myLine = (MoneyWiseQIFFlagLine<?>) pThat;
415 
416             /* Check line type */
417             if (!getLineType().equals(myLine.getLineType())) {
418                 return false;
419             }
420 
421             /* Check value */
422             return isSet.equals(myLine.isSet());
423         }
424 
425         @Override
426         public int hashCode() {
427             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
428             return myResult + isSet.hashCode();
429         }
430     }
431 
432     /**
433      * The Cleared line.
434      *
435      * @param <X> the line type
436      */
437     protected abstract static class MoneyWiseQIFClearedLine<X extends MoneyWiseQLineType>
438             extends MoneyWiseQIFFlagLine<X> {
439         /**
440          * Constructor.
441          *
442          * @param pSet is the flag set?
443          */
444         protected MoneyWiseQIFClearedLine(final Boolean pSet) {
445             /* Call super-constructor */
446             super(pSet);
447         }
448 
449         /**
450          * Obtain Cleared status.
451          *
452          * @return true/false
453          */
454         public Boolean isCleared() {
455             return isSet();
456         }
457 
458         @Override
459         protected void formatData(final OceanusDataFormatter pFormatter,
460                                   final StringBuilder pBuilder) {
461             /* If we should set the flag */
462             if (isSet()) {
463                 /* Add the flag */
464                 pBuilder.append(QIF_RECONCILED);
465             }
466         }
467     }
468 
469     /**
470      * The Price line.
471      *
472      * @param <X> the line type
473      */
474     protected abstract static class MoneyWiseQIFPriceLine<X extends MoneyWiseQLineType>
475             extends MoneyWiseQIFLine<X> {
476         /**
477          * The price.
478          */
479         private final OceanusPrice thePrice;
480 
481         /**
482          * Constructor.
483          *
484          * @param pPrice the Price
485          */
486         protected MoneyWiseQIFPriceLine(final OceanusPrice pPrice) {
487             /* Store data */
488             thePrice = pPrice;
489         }
490 
491         @Override
492         public String toString() {
493             return getPrice().toString();
494         }
495 
496         /**
497          * Obtain price.
498          *
499          * @return the price
500          */
501         protected OceanusPrice getPrice() {
502             return thePrice;
503         }
504 
505         @Override
506         protected void formatData(final OceanusDataFormatter pFormatter,
507                                   final StringBuilder pBuilder) {
508             /* Convert to Decimal */
509             final OceanusDecimal myDecimal = new OceanusDecimal(thePrice);
510 
511             /* Append the string data */
512             pBuilder.append(pFormatter.formatObject(myDecimal));
513         }
514 
515         @Override
516         public boolean equals(final Object pThat) {
517             /* Handle trivial cases */
518             if (this == pThat) {
519                 return true;
520             }
521             if (pThat == null) {
522                 return false;
523             }
524 
525             /* Check class */
526             if (!getClass().equals(pThat.getClass())) {
527                 return false;
528             }
529 
530             /* Cast correctly */
531             final MoneyWiseQIFPriceLine<?> myLine = (MoneyWiseQIFPriceLine<?>) pThat;
532 
533             /* Check line type */
534             if (!getLineType().equals(myLine.getLineType())) {
535                 return false;
536             }
537 
538             /* Check value */
539             return thePrice.equals(myLine.getPrice());
540         }
541 
542         @Override
543         public int hashCode() {
544             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
545             return myResult + thePrice.hashCode();
546         }
547     }
548 
549     /**
550      * The Units line.
551      *
552      * @param <X> the line type
553      */
554     protected abstract static class MoneyWiseQIFUnitsLine<X extends MoneyWiseQLineType>
555             extends MoneyWiseQIFLine<X> {
556         /**
557          * The units.
558          */
559         private final OceanusUnits theUnits;
560 
561         /**
562          * Constructor.
563          *
564          * @param pUnits the Units
565          */
566         protected MoneyWiseQIFUnitsLine(final OceanusUnits pUnits) {
567             /* Store data */
568             theUnits = pUnits;
569         }
570 
571         @Override
572         public String toString() {
573             return getUnits().toString();
574         }
575 
576         /**
577          * Obtain units.
578          *
579          * @return the units
580          */
581         protected OceanusUnits getUnits() {
582             return theUnits;
583         }
584 
585         @Override
586         protected void formatData(final OceanusDataFormatter pFormatter,
587                                   final StringBuilder pBuilder) {
588             /* Append the string data */
589             pBuilder.append(pFormatter.formatObject(theUnits));
590         }
591 
592         @Override
593         public boolean equals(final Object pThat) {
594             /* Handle trivial cases */
595             if (this == pThat) {
596                 return true;
597             }
598             if (pThat == null) {
599                 return false;
600             }
601 
602             /* Check class */
603             if (!getClass().equals(pThat.getClass())) {
604                 return false;
605             }
606 
607             /* Cast correctly */
608             final MoneyWiseQIFUnitsLine<?> myLine = (MoneyWiseQIFUnitsLine<?>) pThat;
609 
610             /* Check line type */
611             if (!getLineType().equals(myLine.getLineType())) {
612                 return false;
613             }
614 
615             /* Check value */
616             return theUnits.equals(myLine.getUnits());
617         }
618 
619         @Override
620         public int hashCode() {
621             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
622             return myResult + theUnits.hashCode();
623         }
624     }
625 
626     /**
627      * The Rate line.
628      *
629      * @param <X> the line type
630      */
631     protected abstract static class MoneyWiseQIFRateLine<X extends MoneyWiseQLineType>
632             extends MoneyWiseQIFLine<X> {
633         /**
634          * The Rate.
635          */
636         private final OceanusRate theRate;
637 
638         /**
639          * Constructor.
640          *
641          * @param pPercent the percentage
642          */
643         protected MoneyWiseQIFRateLine(final OceanusRate pPercent) {
644             /* Store data */
645             theRate = pPercent;
646         }
647 
648         @Override
649         public String toString() {
650             return getRate().toString();
651         }
652 
653         /**
654          * Obtain rate.
655          *
656          * @return the rate
657          */
658         protected OceanusRate getRate() {
659             return theRate;
660         }
661 
662         @Override
663         protected void formatData(final OceanusDataFormatter pFormatter,
664                                   final StringBuilder pBuilder) {
665             /* Append the string data */
666             pBuilder.append(pFormatter.formatObject(theRate));
667         }
668 
669         @Override
670         public boolean equals(final Object pThat) {
671             /* Handle trivial cases */
672             if (this == pThat) {
673                 return true;
674             }
675             if (pThat == null) {
676                 return false;
677             }
678 
679             /* Check class */
680             if (!getClass().equals(pThat.getClass())) {
681                 return false;
682             }
683 
684             /* Cast correctly */
685             final MoneyWiseQIFRateLine<?> myLine = (MoneyWiseQIFRateLine<?>) pThat;
686 
687             /* Check line type */
688             if (!getLineType().equals(myLine.getLineType())) {
689                 return false;
690             }
691 
692             /* Check value */
693             return theRate.equals(myLine.getRate());
694         }
695 
696         @Override
697         public int hashCode() {
698             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
699             return myResult + theRate.hashCode();
700         }
701     }
702 
703     /**
704      * The Ratio line.
705      *
706      * @param <X> the line type
707      */
708     protected abstract static class MoneyWiseQIFRatioLine<X extends MoneyWiseQLineType>
709             extends MoneyWiseQIFLine<X> {
710         /**
711          * The ratio.
712          */
713         private final OceanusRatio theRatio;
714 
715         /**
716          * Constructor.
717          *
718          * @param pRatio the Ratio
719          */
720         protected MoneyWiseQIFRatioLine(final OceanusRatio pRatio) {
721             /* Store data */
722             theRatio = pRatio;
723         }
724 
725         @Override
726         public String toString() {
727             return getRatio().toString();
728         }
729 
730         /**
731          * Obtain ratio.
732          *
733          * @return the ratio
734          */
735         protected OceanusRatio getRatio() {
736             return theRatio;
737         }
738 
739         @Override
740         protected void formatData(final OceanusDataFormatter pFormatter,
741                                   final StringBuilder pBuilder) {
742             /* Append the string data */
743             pBuilder.append(pFormatter.formatObject(theRatio));
744         }
745 
746         @Override
747         public boolean equals(final Object pThat) {
748             /* Handle trivial cases */
749             if (this == pThat) {
750                 return true;
751             }
752             if (pThat == null) {
753                 return false;
754             }
755 
756             /* Check class */
757             if (!getClass().equals(pThat.getClass())) {
758                 return false;
759             }
760 
761             /* Cast correctly */
762             final MoneyWiseQIFRatioLine<?> myLine = (MoneyWiseQIFRatioLine<?>) pThat;
763 
764             /* Check line type */
765             if (!getLineType().equals(myLine.getLineType())) {
766                 return false;
767             }
768 
769             /* Check value */
770             return theRatio.equals(myLine.getRatio());
771         }
772 
773         @Override
774         public int hashCode() {
775             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
776             return myResult + theRatio.hashCode();
777         }
778     }
779 
780     /**
781      * The Security line.
782      *
783      * @param <X> the line type
784      */
785     public abstract static class MoneyWiseQIFSecurityLine<X extends MoneyWiseQLineType>
786             extends MoneyWiseQIFLine<X> {
787         /**
788          * The security.
789          */
790         private final MoneyWiseQIFSecurity theSecurity;
791 
792         /**
793          * Constructor.
794          *
795          * @param pSecurity the Security
796          */
797         protected MoneyWiseQIFSecurityLine(final MoneyWiseQIFSecurity pSecurity) {
798             /* Store data */
799             theSecurity = pSecurity;
800         }
801 
802         @Override
803         public String toString() {
804             return theSecurity.toString();
805         }
806 
807         /**
808          * Obtain account.
809          *
810          * @return the account
811          */
812         public MoneyWiseQIFSecurity getSecurity() {
813             return theSecurity;
814         }
815 
816         @Override
817         protected void formatData(final OceanusDataFormatter pFormatter,
818                                   final StringBuilder pBuilder) {
819             /* Append the security name */
820             pBuilder.append(theSecurity.getName());
821         }
822 
823         @Override
824         public boolean equals(final Object pThat) {
825             /* Handle trivial cases */
826             if (this == pThat) {
827                 return true;
828             }
829             if (pThat == null) {
830                 return false;
831             }
832 
833             /* Check class */
834             if (!getClass().equals(pThat.getClass())) {
835                 return false;
836             }
837 
838             /* Cast correctly */
839             final MoneyWiseQIFSecurityLine<?> myLine = (MoneyWiseQIFSecurityLine<?>) pThat;
840 
841             /* Check line type */
842             if (!getLineType().equals(myLine.getLineType())) {
843                 return false;
844             }
845 
846             /* Check value */
847             return theSecurity.equals(myLine.getSecurity());
848         }
849 
850         @Override
851         public int hashCode() {
852             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
853             return myResult + theSecurity.hashCode();
854         }
855     }
856 
857     /**
858      * The Account line.
859      *
860      * @param <X> the line type
861      */
862     public abstract static class MoneyWiseQIFXferAccountLine<X extends MoneyWiseQLineType>
863             extends MoneyWiseQIFLine<X> {
864         /**
865          * The account.
866          */
867         private final MoneyWiseQIFAccount theAccount;
868 
869         /**
870          * The class list.
871          */
872         private final List<MoneyWiseQIFClass> theClasses;
873 
874         /**
875          * Constructor.
876          *
877          * @param pAccount the Account
878          */
879         protected MoneyWiseQIFXferAccountLine(final MoneyWiseQIFAccount pAccount) {
880             this(pAccount, null);
881         }
882 
883         /**
884          * Constructor.
885          *
886          * @param pAccount the Account
887          * @param pClasses the classes
888          */
889         protected MoneyWiseQIFXferAccountLine(final MoneyWiseQIFAccount pAccount,
890                                               final List<MoneyWiseQIFClass> pClasses) {
891             /* Store data */
892             theAccount = pAccount;
893             theClasses = pClasses;
894         }
895 
896         @Override
897         public String toString() {
898             return theAccount.toString();
899         }
900 
901         /**
902          * Obtain account.
903          *
904          * @return the account
905          */
906         public MoneyWiseQIFAccount getAccount() {
907             return theAccount;
908         }
909 
910         /**
911          * Obtain class list.
912          *
913          * @return the class list
914          */
915         public List<MoneyWiseQIFClass> getClassList() {
916             return theClasses;
917         }
918 
919         @Override
920         protected void formatData(final OceanusDataFormatter pFormatter,
921                                   final StringBuilder pBuilder) {
922             /* Append the string data */
923             pBuilder.append(QIF_XFERSTART);
924             pBuilder.append(theAccount.getName());
925             pBuilder.append(QIF_XFEREND);
926 
927             /* If we have classes */
928             if (theClasses != null) {
929                 /* Add class indicator */
930                 pBuilder.append(QIF_CLASS);
931 
932                 /* Iterate through the list */
933                 final Iterator<MoneyWiseQIFClass> myIterator = theClasses.iterator();
934                 while (myIterator.hasNext()) {
935                     final MoneyWiseQIFClass myClass = myIterator.next();
936 
937                     /* Add to the list */
938                     pBuilder.append(myClass.getName());
939                     if (myIterator.hasNext()) {
940                         pBuilder.append(QIF_CLASSSEP);
941                     }
942                 }
943             }
944         }
945 
946         /**
947          * Parse account line.
948          *
949          * @param pFile the QIF File definitions
950          * @param pLine the line.
951          * @return the account name (or null)
952          */
953         protected static MoneyWiseQIFAccount parseAccount(final MoneyWiseQIFFile pFile,
954                                                           final String pLine) {
955             /* Determine line to use */
956             String myLine = pLine;
957 
958             /* If the line contains a category separator */
959             if (pLine.contains(QIF_CATSEP)) {
960                 /* Move to data following separator */
961                 final int i = pLine.indexOf(QIF_CATSEP);
962                 myLine = pLine.substring(i + 1);
963             }
964 
965             /* If the line contains classes */
966             if (myLine.contains(QIF_CLASS)) {
967                 /* drop class data */
968                 final int i = myLine.indexOf(QIF_CLASS);
969                 myLine = myLine.substring(0, i);
970             }
971 
972             /* If we have the account delimiters */
973             if ((myLine.startsWith(QIF_XFERSTART))
974                     && (myLine.endsWith(QIF_XFEREND))) {
975                 /* Remove account delimiters */
976                 final int i = QIF_XFERSTART.length();
977                 final int j = QIF_XFEREND.length();
978                 final String myAccount = myLine.substring(i, myLine.length()
979                         - j);
980                 return pFile.getAccount(myAccount);
981             }
982 
983             /* Return no account */
984             return null;
985         }
986 
987         /**
988          * Parse account classes.
989          *
990          * @param pFile the QIF File
991          * @param pLine the line.
992          * @return the account name (or null)
993          */
994         protected static List<MoneyWiseQIFClass> parseAccountClasses(final MoneyWiseQIFFile pFile,
995                                                                      final String pLine) {
996             /* Determine line to use */
997             String myLine = pLine;
998 
999             /* If the line contains a category separator */
1000             if (pLine.contains(QIF_CATSEP)) {
1001                 /* Move to data following separator */
1002                 final int i = pLine.indexOf(QIF_CATSEP);
1003                 myLine = pLine.substring(i + 1);
1004             }
1005 
1006             /* If the line contains classes */
1007             if (myLine.contains(QIF_CLASS)) {
1008                 /* drop preceding data */
1009                 final int i = myLine.indexOf(QIF_CLASS);
1010                 myLine = myLine.substring(i + 1);
1011 
1012                 /* Build list of classes */
1013                 final String[] myClasses = myLine.split(QIF_CLASSSEP);
1014                 final List<MoneyWiseQIFClass> myList = new ArrayList<>();
1015                 for (String myClass : myClasses) {
1016                     myList.add(pFile.getClass(myClass));
1017                 }
1018 
1019                 /* Return the classes */
1020                 return myList;
1021             }
1022 
1023             /* Return no classes */
1024             return null;
1025         }
1026 
1027         @Override
1028         public boolean equals(final Object pThat) {
1029             /* Handle trivial cases */
1030             if (this == pThat) {
1031                 return true;
1032             }
1033             if (pThat == null) {
1034                 return false;
1035             }
1036 
1037             /* Check class */
1038             if (!getClass().equals(pThat.getClass())) {
1039                 return false;
1040             }
1041 
1042             /* Cast correctly */
1043             final MoneyWiseQIFXferAccountLine<?> myLine = (MoneyWiseQIFXferAccountLine<?>) pThat;
1044 
1045             /* Check line type */
1046             if (!getLineType().equals(myLine.getLineType())) {
1047                 return false;
1048             }
1049 
1050             /* Check account */
1051             if (!theAccount.equals(myLine.getAccount())) {
1052                 return false;
1053             }
1054 
1055             /* Check classes */
1056             final List<MoneyWiseQIFClass> myClasses = myLine.getClassList();
1057             if (theClasses == null) {
1058                 return myClasses == null;
1059             } else if (myClasses == null) {
1060                 return true;
1061             }
1062             return theClasses.equals(myClasses);
1063         }
1064 
1065         @Override
1066         public int hashCode() {
1067             int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
1068             if (theClasses != null) {
1069                 myResult += theClasses.hashCode();
1070                 myResult *= MoneyWiseQIFFile.HASH_BASE;
1071             }
1072             return myResult + theAccount.hashCode();
1073         }
1074     }
1075 
1076     /**
1077      * The Payee line.
1078      *
1079      * @param <X> the line type
1080      */
1081     public abstract static class MoneyWiseQIFPayeeLine<X extends MoneyWiseQLineType>
1082             extends MoneyWiseQIFLine<X> {
1083         /**
1084          * The payee.
1085          */
1086         private final MoneyWiseQIFPayee thePayee;
1087 
1088         /**
1089          * Constructor.
1090          *
1091          * @param pPayee the Payee
1092          */
1093         protected MoneyWiseQIFPayeeLine(final MoneyWiseQIFPayee pPayee) {
1094             /* Store data */
1095             thePayee = pPayee;
1096         }
1097 
1098         @Override
1099         public String toString() {
1100             return thePayee.toString();
1101         }
1102 
1103         /**
1104          * Obtain payee.
1105          *
1106          * @return the payee
1107          */
1108         public MoneyWiseQIFPayee getPayee() {
1109             return thePayee;
1110         }
1111 
1112         @Override
1113         protected void formatData(final OceanusDataFormatter pFormatter,
1114                                   final StringBuilder pBuilder) {
1115             /* Append the string data */
1116             pBuilder.append(thePayee.getName());
1117         }
1118 
1119         @Override
1120         public boolean equals(final Object pThat) {
1121             /* Handle trivial cases */
1122             if (this == pThat) {
1123                 return true;
1124             }
1125             if (pThat == null) {
1126                 return false;
1127             }
1128 
1129             /* Check class */
1130             if (!getClass().equals(pThat.getClass())) {
1131                 return false;
1132             }
1133 
1134             /* Cast correctly */
1135             final MoneyWiseQIFPayeeLine<?> myLine = (MoneyWiseQIFPayeeLine<?>) pThat;
1136 
1137             /* Check line type */
1138             if (!getLineType().equals(myLine.getLineType())) {
1139                 return false;
1140             }
1141 
1142             /* Check value */
1143             return thePayee.equals(myLine.getPayee());
1144         }
1145 
1146         @Override
1147         public int hashCode() {
1148             final int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
1149             return myResult + thePayee.hashCode();
1150         }
1151     }
1152 
1153     /**
1154      * The Event Category line.
1155      *
1156      * @param <X> the line type
1157      */
1158     public abstract static class MoneyWiseQIFCategoryLine<X extends MoneyWiseQLineType>
1159             extends MoneyWiseQIFLine<X> {
1160         /**
1161          * The event category.
1162          */
1163         private final MoneyWiseQIFEventCategory theCategory;
1164 
1165         /**
1166          * The class list.
1167          */
1168         private final List<MoneyWiseQIFClass> theClasses;
1169 
1170         /**
1171          * Constructor.
1172          *
1173          * @param pCategory the Event Category
1174          */
1175         protected MoneyWiseQIFCategoryLine(final MoneyWiseQIFEventCategory pCategory) {
1176             this(pCategory, null);
1177         }
1178 
1179         /**
1180          * Constructor.
1181          *
1182          * @param pCategory the Event Category
1183          * @param pClasses  the classes
1184          */
1185         protected MoneyWiseQIFCategoryLine(final MoneyWiseQIFEventCategory pCategory,
1186                                            final List<MoneyWiseQIFClass> pClasses) {
1187             /* Store data */
1188             theCategory = pCategory;
1189             theClasses = pClasses;
1190         }
1191 
1192         @Override
1193         public String toString() {
1194             return theCategory.toString();
1195         }
1196 
1197         /**
1198          * Obtain event category.
1199          *
1200          * @return the event category
1201          */
1202         public MoneyWiseQIFEventCategory getEventCategory() {
1203             return theCategory;
1204         }
1205 
1206         /**
1207          * Obtain class list.
1208          *
1209          * @return the class list
1210          */
1211         public List<MoneyWiseQIFClass> getClassList() {
1212             return theClasses;
1213         }
1214 
1215         @Override
1216         protected void formatData(final OceanusDataFormatter pFormatter,
1217                                   final StringBuilder pBuilder) {
1218             /* Append the string data */
1219             pBuilder.append(theCategory.getName());
1220 
1221             /* If we have classes */
1222             if (theClasses != null) {
1223                 /* Add class indicator */
1224                 pBuilder.append(QIF_CLASS);
1225 
1226                 /* Iterate through the list */
1227                 final Iterator<MoneyWiseQIFClass> myIterator = theClasses.iterator();
1228                 while (myIterator.hasNext()) {
1229                     final MoneyWiseQIFClass myClass = myIterator.next();
1230 
1231                     /* Add to the list */
1232                     pBuilder.append(myClass.getName());
1233                     if (myIterator.hasNext()) {
1234                         pBuilder.append(QIF_CLASSSEP);
1235                     }
1236                 }
1237             }
1238         }
1239 
1240         /**
1241          * Parse category line.
1242          *
1243          * @param pFile the QIF File
1244          * @param pLine the line.
1245          * @return the account name (or null)
1246          */
1247         protected static MoneyWiseQIFEventCategory parseCategory(final MoneyWiseQIFFile pFile,
1248                                                                  final String pLine) {
1249             /* Determine line to use */
1250             String myLine = pLine;
1251 
1252             /* If the line contains a category separator */
1253             if (pLine.contains(QIF_CATSEP)) {
1254                 /* Drop data after separator */
1255                 final int i = pLine.indexOf(QIF_CATSEP);
1256                 myLine = pLine.substring(0, i);
1257             }
1258 
1259             /* If the line contains classes */
1260             if (myLine.contains(QIF_CLASS)) {
1261                 /* drop class data */
1262                 final int i = myLine.indexOf(QIF_CLASS);
1263                 myLine = myLine.substring(0, i);
1264             }
1265 
1266             /* If we have the account delimiters */
1267             if ((myLine.startsWith(QIF_XFERSTART))
1268                     && (myLine.endsWith(QIF_XFEREND))) {
1269                 /* This is an account */
1270                 return null;
1271             }
1272 
1273             /* Return category */
1274             return pFile.getCategory(myLine);
1275         }
1276 
1277         /**
1278          * Parse category classes.
1279          *
1280          * @param pFile the QIF File
1281          * @param pLine the line.
1282          * @return the account name (or null)
1283          */
1284         protected static List<MoneyWiseQIFClass> parseCategoryClasses(final MoneyWiseQIFFile pFile,
1285                                                                       final String pLine) {
1286             /* Determine line to use */
1287             String myLine = pLine;
1288 
1289             /* If the line contains a category separator */
1290             if (pLine.contains(QIF_CATSEP)) {
1291                 /* Drop data after separator */
1292                 final int i = pLine.indexOf(QIF_CATSEP);
1293                 myLine = pLine.substring(0, i);
1294             }
1295 
1296             /* If the line contains classes */
1297             if (myLine.contains(QIF_CLASS)) {
1298                 /* drop preceding data */
1299                 final int i = myLine.indexOf(QIF_CLASS);
1300                 myLine = myLine.substring(i + 1);
1301 
1302                 /* Build list of classes */
1303                 final String[] myClasses = myLine.split(QIF_CLASSSEP);
1304                 final List<MoneyWiseQIFClass> myList = new ArrayList<>();
1305                 for (String myClass : myClasses) {
1306                     myList.add(pFile.getClass(myClass));
1307                 }
1308 
1309                 /* Return the classes */
1310                 return myList;
1311             }
1312 
1313             /* Return no classes */
1314             return null;
1315         }
1316 
1317         @Override
1318         public boolean equals(final Object pThat) {
1319             /* Handle trivial cases */
1320             if (this == pThat) {
1321                 return true;
1322             }
1323             if (pThat == null) {
1324                 return false;
1325             }
1326 
1327             /* Check class */
1328             if (!getClass().equals(pThat.getClass())) {
1329                 return false;
1330             }
1331 
1332             /* Cast correctly */
1333             final MoneyWiseQIFCategoryLine<?> myLine = (MoneyWiseQIFCategoryLine<?>) pThat;
1334 
1335             /* Check line type */
1336             if (!getLineType().equals(myLine.getLineType())) {
1337                 return false;
1338             }
1339 
1340             /* Check category */
1341             if (!theCategory.equals(myLine.getEventCategory())) {
1342                 return false;
1343             }
1344 
1345             /* Check classes */
1346             final List<MoneyWiseQIFClass> myClasses = myLine.getClassList();
1347             if (theClasses == null) {
1348                 return myClasses == null;
1349             } else if (myClasses == null) {
1350                 return true;
1351             }
1352             return theClasses.equals(myClasses);
1353         }
1354 
1355         @Override
1356         public int hashCode() {
1357             int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
1358             if (theClasses != null) {
1359                 myResult += theClasses.hashCode();
1360                 myResult *= MoneyWiseQIFFile.HASH_BASE;
1361             }
1362             return myResult + theCategory.hashCode();
1363         }
1364     }
1365 
1366     /**
1367      * The Event Category line.
1368      *
1369      * @param <X> the line type
1370      */
1371     public abstract static class MoneyWiseQIFCategoryAccountLine<X extends MoneyWiseQLineType>
1372             extends MoneyWiseQIFLine<X> {
1373         /**
1374          * The event category.
1375          */
1376         private final MoneyWiseQIFEventCategory theCategory;
1377 
1378         /**
1379          * The account.
1380          */
1381         private final MoneyWiseQIFAccount theAccount;
1382 
1383         /**
1384          * The class list.
1385          */
1386         private final List<MoneyWiseQIFClass> theClasses;
1387 
1388         /**
1389          * Constructor.
1390          *
1391          * @param pCategory the Event Category
1392          * @param pAccount  the Account
1393          */
1394         protected MoneyWiseQIFCategoryAccountLine(final MoneyWiseQIFEventCategory pCategory,
1395                                                   final MoneyWiseQIFAccount pAccount) {
1396             this(pCategory, pAccount, null);
1397         }
1398 
1399         /**
1400          * Constructor.
1401          *
1402          * @param pCategory the Event Category
1403          * @param pAccount  the Account
1404          * @param pClasses  the classes
1405          */
1406         protected MoneyWiseQIFCategoryAccountLine(final MoneyWiseQIFEventCategory pCategory,
1407                                                   final MoneyWiseQIFAccount pAccount,
1408                                                   final List<MoneyWiseQIFClass> pClasses) {
1409             /* Store data */
1410             theCategory = pCategory;
1411             theAccount = pAccount;
1412             theClasses = pClasses;
1413         }
1414 
1415         /**
1416          * Obtain event category.
1417          *
1418          * @return the event category
1419          */
1420         public MoneyWiseQIFEventCategory getEventCategory() {
1421             return theCategory;
1422         }
1423 
1424         /**
1425          * Obtain account.
1426          *
1427          * @return the account
1428          */
1429         public MoneyWiseQIFAccount getAccount() {
1430             return theAccount;
1431         }
1432 
1433         /**
1434          * Obtain class list.
1435          *
1436          * @return the class list
1437          */
1438         public List<MoneyWiseQIFClass> getClassList() {
1439             return theClasses;
1440         }
1441 
1442         @Override
1443         protected void formatData(final OceanusDataFormatter pFormatter,
1444                                   final StringBuilder pBuilder) {
1445             /* Append the string data */
1446             pBuilder.append(theCategory.getName());
1447             pBuilder.append(QIF_CATSEP);
1448             pBuilder.append(QIF_XFERSTART);
1449             pBuilder.append(theAccount.getName());
1450             pBuilder.append(QIF_XFEREND);
1451 
1452             /* If we have classes */
1453             if (theClasses != null) {
1454                 /* Add class indicator */
1455                 pBuilder.append(QIF_CLASS);
1456 
1457                 /* Iterate through the list */
1458                 final Iterator<MoneyWiseQIFClass> myIterator = theClasses.iterator();
1459                 while (myIterator.hasNext()) {
1460                     final MoneyWiseQIFClass myClass = myIterator.next();
1461 
1462                     /* Add to the list */
1463                     pBuilder.append(myClass.getName());
1464                     if (myIterator.hasNext()) {
1465                         pBuilder.append(QIF_CLASSSEP);
1466                     }
1467                 }
1468             }
1469         }
1470 
1471         @Override
1472         public boolean equals(final Object pThat) {
1473             /* Handle trivial cases */
1474             if (this == pThat) {
1475                 return true;
1476             }
1477             if (pThat == null) {
1478                 return false;
1479             }
1480 
1481             /* Check class */
1482             if (!getClass().equals(pThat.getClass())) {
1483                 return false;
1484             }
1485 
1486             /* Cast correctly */
1487             final MoneyWiseQIFCategoryAccountLine<?> myLine = (MoneyWiseQIFCategoryAccountLine<?>) pThat;
1488 
1489             /* Check line type */
1490             if (!getLineType().equals(myLine.getLineType())) {
1491                 return false;
1492             }
1493 
1494             /* Check category */
1495             if (!theCategory.equals(myLine.getEventCategory())) {
1496                 return false;
1497             }
1498 
1499             /* Check account */
1500             if (!theAccount.equals(myLine.getAccount())) {
1501                 return false;
1502             }
1503 
1504             /* Check classes */
1505             final List<MoneyWiseQIFClass> myClasses = myLine.getClassList();
1506             if (theClasses == null) {
1507                 return myClasses == null;
1508             } else if (myClasses == null) {
1509                 return true;
1510             }
1511             return theClasses.equals(myClasses);
1512         }
1513 
1514         @Override
1515         public int hashCode() {
1516             int myResult = MoneyWiseQIFFile.HASH_BASE * getLineType().hashCode();
1517             if (theClasses != null) {
1518                 myResult += theClasses.hashCode();
1519                 myResult *= MoneyWiseQIFFile.HASH_BASE;
1520             }
1521             myResult += theAccount.hashCode();
1522             myResult *= MoneyWiseQIFFile.HASH_BASE;
1523             return myResult + theCategory.hashCode();
1524         }
1525     }
1526 }