]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java
guard against ncurrent navigation controller destination being null
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / model / PatternDetailsItem.java
1 /*
2  * Copyright © 2021 Damyan Ivanov.
3  * This file is part of MoLe.
4  * MoLe is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * MoLe is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.model;
19
20 import android.content.res.Resources;
21
22 import androidx.annotation.NonNull;
23
24 import net.ktnx.mobileledger.R;
25 import net.ktnx.mobileledger.db.PatternAccount;
26 import net.ktnx.mobileledger.db.PatternBase;
27 import net.ktnx.mobileledger.db.PatternHeader;
28 import net.ktnx.mobileledger.utils.Misc;
29
30 import org.jetbrains.annotations.Contract;
31 import org.jetbrains.annotations.NotNull;
32
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 import java.util.regex.PatternSyntaxException;
36
37 abstract public class PatternDetailsItem {
38     private final Type type;
39     protected Long id;
40     protected Long position;
41
42     protected PatternDetailsItem(Type type) {
43         this.type = type;
44     }
45     @Contract(" -> new")
46     public static @NotNull PatternDetailsItem.Header createHeader() {
47         return new Header();
48     }
49     public static @NotNull PatternDetailsItem.Header createHeader(Header origin) {
50         return new Header(origin);
51     }
52     @Contract("-> new")
53     public static @NotNull PatternDetailsItem.AccountRow createAccountRow() {
54         return new AccountRow();
55     }
56     public static PatternDetailsItem fromRoomObject(PatternBase p) {
57         if (p instanceof PatternHeader) {
58             PatternHeader ph = (PatternHeader) p;
59             Header header = createHeader();
60             header.setId(ph.getId());
61             header.setName(ph.getName());
62             header.setPattern(ph.getRegularExpression());
63             header.setTestText(ph.getTestText());
64
65             if (ph.getTransactionDescriptionMatchGroup() == null)
66                 header.setTransactionDescription(ph.getTransactionDescription());
67             else
68                 header.setTransactionDescriptionMatchGroup(
69                         ph.getTransactionDescriptionMatchGroup());
70
71             if (ph.getTransactionCommentMatchGroup() == null)
72                 header.setTransactionComment(ph.getTransactionComment());
73             else
74                 header.setTransactionCommentMatchGroup(ph.getTransactionCommentMatchGroup());
75
76             if (ph.getDateDayMatchGroup() == null)
77                 header.setDateDay(ph.getDateDay());
78             else
79                 header.setDateDayMatchGroup(ph.getDateDayMatchGroup());
80
81             if (ph.getDateMonthMatchGroup() == null)
82                 header.setDateMonth(ph.getDateMonth());
83             else
84                 header.setDateMonthMatchGroup(ph.getDateMonthMatchGroup());
85
86             if (ph.getDateYearMatchGroup() == null)
87                 header.setDateYear(ph.getDateYear());
88             else
89                 header.setDateYearMatchGroup(ph.getDateYearMatchGroup());
90
91             return header;
92         }
93         else if (p instanceof PatternAccount) {
94             PatternAccount pa = (PatternAccount) p;
95             AccountRow acc = createAccountRow();
96             acc.setId(pa.getId());
97
98             if (pa.getAccountNameMatchGroup() == null)
99                 acc.setAccountName(Misc.nullIsEmpty(pa.getAccountName()));
100             else
101                 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
102
103             if (pa.getAccountCommentMatchGroup() == null)
104                 acc.setAccountComment(Misc.nullIsEmpty(pa.getAccountComment()));
105             else
106                 acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
107
108             if (pa.getCurrencyMatchGroup() == null) {
109                 final Integer currencyId = pa.getCurrency();
110                 if (currencyId != null && currencyId > 0)
111                     acc.setCurrency(Currency.loadById(currencyId));
112             }
113             else
114                 acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
115
116             final Integer amountMatchGroup = pa.getAmountMatchGroup();
117             if (amountMatchGroup != null && amountMatchGroup > 0) {
118                 acc.setAmountMatchGroup(amountMatchGroup);
119                 final Boolean negateAmount = pa.getNegateAmount();
120                 acc.setNegateAmount(negateAmount != null && negateAmount);
121             }
122             else
123                 acc.setAmount(pa.getAmount());
124
125             return acc;
126         }
127         else {
128             throw new IllegalStateException("Unexpected item class " + p.getClass());
129         }
130     }
131     public Header asHeaderItem() {
132         ensureType(Type.HEADER);
133         return (Header) this;
134     }
135     public AccountRow asAccountRowItem() {
136         ensureType(Type.ACCOUNT_ITEM);
137         return (AccountRow) this;
138     }
139     private void ensureType(Type type) {
140         if (this.type != type)
141             throw new IllegalStateException(
142                     String.format("Type is %s, but %s is required", this.type.toString(),
143                             type.toString()));
144     }
145     void ensureTrue(boolean flag) {
146         if (!flag)
147             throw new IllegalStateException(
148                     "Literal value requested, but it is matched via a pattern group");
149     }
150     void ensureFalse(boolean flag) {
151         if (flag)
152             throw new IllegalStateException("Matching group requested, but the value is a literal");
153     }
154     public long getId() {
155         return id;
156     }
157     public void setId(Long id) {
158         this.id = id;
159     }
160     public void setId(int id) {
161         this.id = (long) id;
162     }
163     public long getPosition() {
164         return position;
165     }
166     public void setPosition(Long position) {
167         this.position = position;
168     }
169     abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
170     public Type getType() {
171         return type;
172     }
173     public enum Type {
174         HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
175         final int index;
176         Type(int i) {
177             index = i;
178         }
179         public int toInt() {
180             return index;
181         }
182     }
183
184     static class PossiblyMatchedValue<T> {
185         private boolean literalValue;
186         private T value;
187         private int matchGroup;
188         public PossiblyMatchedValue() {
189             literalValue = true;
190             value = null;
191         }
192         public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
193             literalValue = origin.literalValue;
194             value = origin.value;
195             matchGroup = origin.matchGroup;
196         }
197         @NonNull
198         public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
199             PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
200             result.setValue(initialValue);
201             return result;
202         }
203         @NonNull
204         public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
205             PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
206             result.setValue(initialValue);
207             return result;
208         }
209         public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
210             PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
211             result.setValue(initialValue);
212             return result;
213         }
214         @NonNull
215         public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
216             PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
217             result.setValue(initialValue);
218             return result;
219         }
220         public T getValue() {
221             if (!literalValue)
222                 throw new IllegalStateException("Value is not literal");
223             return value;
224         }
225         public void setValue(T newValue) {
226             value = newValue;
227             literalValue = true;
228         }
229         public boolean hasLiteralValue() {
230             return literalValue;
231         }
232         public int getMatchGroup() {
233             if (literalValue)
234                 throw new IllegalStateException("Value is literal");
235             return matchGroup;
236         }
237         public void setMatchGroup(int group) {
238             this.matchGroup = group;
239             literalValue = false;
240         }
241         public boolean equals(PossiblyMatchedValue<T> other) {
242             if (!other.literalValue == literalValue)
243                 return false;
244             if (literalValue) {
245                 if (value == null)
246                     return other.value == null;
247                 return value.equals(other.value);
248             }
249             else
250                 return matchGroup == other.matchGroup;
251         }
252         public void switchToLiteral() {
253             literalValue = true;
254         }
255         public String toString() {
256             if (literalValue)
257                 if (value == null)
258                     return "<null>";
259                 else
260                     return value.toString();
261             if (matchGroup > 0)
262                 return "grp:" + matchGroup;
263             return "<null>";
264         }
265     }
266
267     public static class TYPE {
268         public static final int header = 0;
269         public static final int accountItem = 1;
270     }
271
272     public static class AccountRow extends PatternDetailsItem {
273         private final PossiblyMatchedValue<String> accountName =
274                 PossiblyMatchedValue.withLiteralString("");
275         private final PossiblyMatchedValue<String> accountComment =
276                 PossiblyMatchedValue.withLiteralString("");
277         private final PossiblyMatchedValue<Float> amount =
278                 PossiblyMatchedValue.withLiteralFloat(0f);
279         private final PossiblyMatchedValue<Currency> currency = new PossiblyMatchedValue<>();
280         private boolean negateAmount;
281         private AccountRow() {
282             super(Type.ACCOUNT_ITEM);
283         }
284         public boolean isNegateAmount() {
285             return negateAmount;
286         }
287         public void setNegateAmount(boolean negateAmount) {
288             this.negateAmount = negateAmount;
289         }
290         public int getAccountCommentMatchGroup() {
291             return accountComment.getMatchGroup();
292         }
293         public void setAccountCommentMatchGroup(int group) {
294             accountComment.setMatchGroup(group);
295         }
296         public String getAccountComment() {
297             return accountComment.getValue();
298         }
299         public void setAccountComment(String comment) {
300             this.accountComment.setValue(comment);
301         }
302         public int getCurrencyMatchGroup() {
303             return currency.getMatchGroup();
304         }
305         public void setCurrencyMatchGroup(int group) {
306             currency.setMatchGroup(group);
307         }
308         public Currency getCurrency() {
309             return currency.getValue();
310         }
311         public void setCurrency(Currency currency) {
312             this.currency.setValue(currency);
313         }
314         public int getAccountNameMatchGroup() {
315             return accountName.getMatchGroup();
316         }
317         public void setAccountNameMatchGroup(int group) {
318             accountName.setMatchGroup(group);
319         }
320         public String getAccountName() {
321             return accountName.getValue();
322         }
323         public void setAccountName(String accountName) {
324             this.accountName.setValue(accountName);
325         }
326         public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
327         public boolean hasLiteralAmount() {
328             return amount.hasLiteralValue();
329         }
330         public int getAmountMatchGroup() {
331             return amount.getMatchGroup();
332         }
333         public void setAmountMatchGroup(int group) {
334             amount.setMatchGroup(group);
335         }
336         public Float getAmount() {
337             return amount.getValue();
338         }
339         public void setAmount(Float amount) {
340             this.amount.setValue(amount);
341         }
342         public String getProblem(@NonNull Resources r, int patternGroupCount) {
343             if (Misc.emptyIsNull(accountName.getValue()) == null)
344                 return r.getString(R.string.account_name_is_empty);
345             if (!amount.hasLiteralValue() &&
346                 (amount.getMatchGroup() < 1 || amount.getMatchGroup() > patternGroupCount))
347                 return r.getString(R.string.invalid_matching_group_number);
348
349             return null;
350         }
351         public boolean hasLiteralAccountComment() {
352             return accountComment.hasLiteralValue();
353         }
354         public boolean equalContents(AccountRow o) {
355             return amount.equals(o.amount) && accountName.equals(o.accountName) &&
356                    accountComment.equals(o.accountComment) && negateAmount == o.negateAmount;
357         }
358         public void switchToLiteralAmount() {
359             amount.switchToLiteral();
360         }
361         public void switchToLiteralAccountName() {
362             accountName.switchToLiteral();
363         }
364         public void switchToLiteralAccountComment() {
365             accountComment.switchToLiteral();
366         }
367         public PatternAccount toDBO(@NonNull Long patternId) {
368             PatternAccount result = new PatternAccount(id, patternId, position);
369
370             if (accountName.hasLiteralValue())
371                 result.setAccountName(accountName.getValue());
372             else
373                 result.setAccountNameMatchGroup(accountName.getMatchGroup());
374
375             if (accountComment.hasLiteralValue())
376                 result.setAccountComment(accountComment.getValue());
377             else
378                 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
379
380             if (amount.hasLiteralValue()) {
381                 result.setAmount(amount.getValue());
382                 result.setNegateAmount(null);
383             }
384             else {
385                 result.setAmountMatchGroup(amount.getMatchGroup());
386                 result.setNegateAmount(negateAmount ? true : null);
387             }
388
389             return result;
390         }
391     }
392
393     public static class Header extends PatternDetailsItem {
394         private String pattern = "";
395         private String testText = "";
396         private Pattern compiledPattern;
397         private String patternError;
398         private String name = "";
399         private PossiblyMatchedValue<String> transactionDescription =
400                 PossiblyMatchedValue.withLiteralString("");
401         private PossiblyMatchedValue<String> transactionComment =
402                 PossiblyMatchedValue.withLiteralString("");
403         private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
404         private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
405         private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
406         private Header() {
407             super(Type.HEADER);
408         }
409         public Header(Header origin) {
410             this();
411             id = origin.id;
412             name = origin.name;
413             testText = origin.testText;
414             setPattern(origin.pattern);
415
416             transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
417             transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
418
419             dateYear = new PossiblyMatchedValue<>(origin.dateYear);
420             dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
421             dateDay = new PossiblyMatchedValue<>(origin.dateDay);
422         }
423         public String getName() {
424             return name;
425         }
426         public void setName(String name) {
427             this.name = name;
428         }
429         public String getPattern() {
430             return pattern;
431         }
432         public void setPattern(String pattern) {
433             this.pattern = pattern;
434             if (pattern != null) {
435                 try {
436                     this.compiledPattern = Pattern.compile(pattern);
437                     this.patternError = null;
438                 }
439                 catch (PatternSyntaxException e) {
440                     this.compiledPattern = null;
441                     this.patternError = e.getMessage();
442                 }
443             }
444             else {
445                 patternError = "Missing pattern";
446             }
447         }
448         @NonNull
449         @Override
450         public String toString() {
451             return super.toString() +
452                    String.format(" name[%s] pat[%s] test[%s] tran[%s] com[%s]", name, pattern,
453                            testText, transactionDescription, transactionComment);
454         }
455         public String getTestText() {
456             return testText;
457         }
458         public void setTestText(String testText) {
459             this.testText = testText;
460         }
461         public String getTransactionDescription() {
462             return transactionDescription.getValue();
463         }
464         public void setTransactionDescription(String transactionDescription) {
465             this.transactionDescription.setValue(transactionDescription);
466         }
467         public String getTransactionComment() {
468             return transactionComment.getValue();
469         }
470         public void setTransactionComment(String transactionComment) {
471             this.transactionComment.setValue(transactionComment);
472         }
473         public Integer getDateYear() {
474             return dateYear.getValue();
475         }
476         public void setDateYear(Integer dateYear) {
477             this.dateYear.setValue(dateYear);
478         }
479         public Integer getDateMonth() {
480             return dateMonth.getValue();
481         }
482         public void setDateMonth(Integer dateMonth) {
483             this.dateMonth.setValue(dateMonth);
484         }
485         public Integer getDateDay() {
486             return dateDay.getValue();
487         }
488         public void setDateDay(Integer dateDay) {
489             this.dateDay.setValue(dateDay);
490         }
491         public int getDateYearMatchGroup() {
492             return dateYear.getMatchGroup();
493         }
494         public void setDateYearMatchGroup(int dateYearMatchGroup) {
495             this.dateYear.setMatchGroup(dateYearMatchGroup);
496         }
497         public int getDateMonthMatchGroup() {
498             return dateMonth.getMatchGroup();
499         }
500         public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
501             this.dateMonth.setMatchGroup(dateMonthMatchGroup);
502         }
503         public int getDateDayMatchGroup() {
504             return dateDay.getMatchGroup();
505         }
506         public void setDateDayMatchGroup(int dateDayMatchGroup) {
507             this.dateDay.setMatchGroup(dateDayMatchGroup);
508         }
509         public boolean hasLiteralDateYear() {
510             return dateYear.hasLiteralValue();
511         }
512         public boolean hasLiteralDateMonth() {
513             return dateMonth.hasLiteralValue();
514         }
515         public boolean hasLiteralDateDay() {
516             return dateDay.hasLiteralValue();
517         }
518         public boolean hasLiteralTransactionDescription() { return transactionDescription.hasLiteralValue(); }
519         public boolean hasLiteralTransactionComment() { return transactionComment.hasLiteralValue(); }
520         public String getProblem(@NonNull Resources r, int patternGroupCount) {
521             if (patternError != null)
522                 return r.getString(R.string.pattern_has_errors) + ": " + patternError;
523             if (Misc.emptyIsNull(pattern) == null)
524                 return r.getString(R.string.pattern_is_empty);
525
526             if (!dateYear.hasLiteralValue() && compiledPattern != null &&
527                 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
528                 return r.getString(R.string.invalid_matching_group_number);
529
530             if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
531                 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
532                 return r.getString(R.string.invalid_matching_group_number);
533
534             if (!dateDay.hasLiteralValue() && compiledPattern != null &&
535                 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
536                 return r.getString(R.string.invalid_matching_group_number);
537
538             return null;
539         }
540
541         public boolean equalContents(Header o) {
542             if (!dateDay.equals(o.dateDay))
543                 return false;
544             if (!dateMonth.equals(o.dateMonth))
545                 return false;
546             if (!dateYear.equals(o.dateYear))
547                 return false;
548             if (!transactionDescription.equals(o.transactionDescription))
549                 return false;
550             if (!transactionComment.equals(o.transactionComment))
551                 return true;
552
553             return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
554                    Misc.equalStrings(testText, o.testText);
555         }
556         public String getMatchGroupText(int group) {
557             if (compiledPattern != null && testText != null) {
558                 Matcher m = compiledPattern.matcher(testText);
559                 if (m.matches())
560                     return m.group(group);
561             }
562
563             return "ø";
564         }
565         public Pattern getCompiledPattern() {
566             return compiledPattern;
567         }
568         public void switchToLiteralTransactionDescription() {
569             transactionDescription.switchToLiteral();
570         }
571         public void switchToLiteralTransactionComment() {
572             transactionComment.switchToLiteral();
573         }
574         public int getTransactionDescriptionMatchGroup() {
575             return transactionDescription.getMatchGroup();
576         }
577         public void setTransactionDescriptionMatchGroup(int group) {
578             transactionDescription.setMatchGroup(group);
579         }
580         public int getTransactionCommentMatchGroup() {
581             return transactionComment.getMatchGroup();
582         }
583         public void setTransactionCommentMatchGroup(int group) {
584             transactionComment.setMatchGroup(group);
585         }
586         public void switchToLiteralDateYear() {
587             dateYear.switchToLiteral();
588         }
589         public void switchToLiteralDateMonth() {
590             dateMonth.switchToLiteral();
591         }
592         public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
593         public PatternHeader toDBO() {
594             PatternHeader result = new PatternHeader(id, name, pattern);
595
596             if (Misc.emptyIsNull(testText) != null)
597                 result.setTestText(testText);
598
599             if (transactionDescription.hasLiteralValue())
600                 result.setTransactionDescription(transactionDescription.getValue());
601             else
602                 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
603
604             if (transactionComment.hasLiteralValue())
605                 result.setTransactionComment(transactionComment.getValue());
606             else
607                 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
608
609             if (dateYear.hasLiteralValue())
610                 result.setDateYear(dateYear.getValue());
611             else
612                 result.setDateYearMatchGroup(dateYear.getMatchGroup());
613
614             if (dateMonth.hasLiteralValue())
615                 result.setDateMonth(dateMonth.getValue());
616             else
617                 result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
618
619             if (dateDay.hasLiteralValue())
620                 result.setDateDay(dateDay.getValue());
621             else
622                 result.setDateDayMatchGroup(dateDay.getMatchGroup());
623
624             return result;
625         }
626     }
627 }