]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java
52fa1dc7671c1f877be9590eb22a179dae95223e
[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             else
120                 acc.setAmount(pa.getAmount());
121
122             return acc;
123         }
124         else {
125             throw new IllegalStateException("Unexpected item class " + p.getClass());
126         }
127     }
128     public Header asHeaderItem() {
129         ensureType(Type.HEADER);
130         return (Header) this;
131     }
132     public AccountRow asAccountRowItem() {
133         ensureType(Type.ACCOUNT_ITEM);
134         return (AccountRow) this;
135     }
136     private void ensureType(Type type) {
137         if (this.type != type)
138             throw new IllegalStateException(
139                     String.format("Type is %s, but %s is required", this.type.toString(),
140                             type.toString()));
141     }
142     void ensureTrue(boolean flag) {
143         if (!flag)
144             throw new IllegalStateException(
145                     "Literal value requested, but it is matched via a pattern group");
146     }
147     void ensureFalse(boolean flag) {
148         if (flag)
149             throw new IllegalStateException("Matching group requested, but the value is a literal");
150     }
151     public long getId() {
152         return id;
153     }
154     public void setId(Long id) {
155         this.id = id;
156     }
157     public void setId(int id) {
158         this.id = (long) id;
159     }
160     public long getPosition() {
161         return position;
162     }
163     public void setPosition(Long position) {
164         this.position = position;
165     }
166     abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
167     public Type getType() {
168         return type;
169     }
170     public enum Type {
171         HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
172         final int index;
173         Type(int i) {
174             index = i;
175         }
176         public int toInt() {
177             return index;
178         }
179     }
180
181     static class PossiblyMatchedValue<T> {
182         private boolean literalValue;
183         private T value;
184         private int matchGroup;
185         public PossiblyMatchedValue() {
186             literalValue = true;
187             value = null;
188         }
189         public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
190             literalValue = origin.literalValue;
191             value = origin.value;
192             matchGroup = origin.matchGroup;
193         }
194         @NonNull
195         public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
196             PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
197             result.setValue(initialValue);
198             return result;
199         }
200         @NonNull
201         public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
202             PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
203             result.setValue(initialValue);
204             return result;
205         }
206         public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
207             PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
208             result.setValue(initialValue);
209             return result;
210         }
211         @NonNull
212         public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
213             PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
214             result.setValue(initialValue);
215             return result;
216         }
217         public T getValue() {
218             if (!literalValue)
219                 throw new IllegalStateException("Value is not literal");
220             return value;
221         }
222         public void setValue(T newValue) {
223             value = newValue;
224             literalValue = true;
225         }
226         public boolean hasLiteralValue() {
227             return literalValue;
228         }
229         public int getMatchGroup() {
230             if (literalValue)
231                 throw new IllegalStateException("Value is literal");
232             return matchGroup;
233         }
234         public void setMatchGroup(int group) {
235             this.matchGroup = group;
236             literalValue = false;
237         }
238         public boolean equals(PossiblyMatchedValue<T> other) {
239             if (!other.literalValue == literalValue)
240                 return false;
241             if (literalValue) {
242                 if (value == null)
243                     return other.value == null;
244                 return value.equals(other.value);
245             }
246             else
247                 return matchGroup == other.matchGroup;
248         }
249         public void switchToLiteral() {
250             literalValue = true;
251         }
252         public String toString() {
253             if (literalValue)
254                 if (value == null)
255                     return "<null>";
256                 else
257                     return value.toString();
258             if (matchGroup > 0)
259                 return "grp:" + matchGroup;
260             return "<null>";
261         }
262     }
263
264     public static class TYPE {
265         public static final int header = 0;
266         public static final int accountItem = 1;
267     }
268
269     public static class AccountRow extends PatternDetailsItem {
270         private final PossiblyMatchedValue<String> accountName =
271                 PossiblyMatchedValue.withLiteralString("");
272         private final PossiblyMatchedValue<String> accountComment =
273                 PossiblyMatchedValue.withLiteralString("");
274         private final PossiblyMatchedValue<Float> amount =
275                 PossiblyMatchedValue.withLiteralFloat(0f);
276         private final PossiblyMatchedValue<Currency> currency = new PossiblyMatchedValue<>();
277         private AccountRow() {
278             super(Type.ACCOUNT_ITEM);
279         }
280         public int getAccountCommentMatchGroup() {
281             return accountComment.getMatchGroup();
282         }
283         public void setAccountCommentMatchGroup(int group) {
284             accountComment.setMatchGroup(group);
285         }
286         public String getAccountComment() {
287             return accountComment.getValue();
288         }
289         public void setAccountComment(String comment) {
290             this.accountComment.setValue(comment);
291         }
292         public int getCurrencyMatchGroup() {
293             return currency.getMatchGroup();
294         }
295         public void setCurrencyMatchGroup(int group) {
296             currency.setMatchGroup(group);
297         }
298         public Currency getCurrency() {
299             return currency.getValue();
300         }
301         public void setCurrency(Currency currency) {
302             this.currency.setValue(currency);
303         }
304         public int getAccountNameMatchGroup() {
305             return accountName.getMatchGroup();
306         }
307         public void setAccountNameMatchGroup(int group) {
308             accountName.setMatchGroup(group);
309         }
310         public String getAccountName() {
311             return accountName.getValue();
312         }
313         public void setAccountName(String accountName) {
314             this.accountName.setValue(accountName);
315         }
316         public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
317         public boolean hasLiteralAmount() {
318             return amount.hasLiteralValue();
319         }
320         public int getAmountMatchGroup() {
321             return amount.getMatchGroup();
322         }
323         public void setAmountMatchGroup(int group) {
324             amount.setMatchGroup(group);
325         }
326         public Float getAmount() {
327             return amount.getValue();
328         }
329         public void setAmount(Float amount) {
330             this.amount.setValue(amount);
331         }
332         public String getProblem(@NonNull Resources r, int patternGroupCount) {
333             if (Misc.emptyIsNull(accountName.getValue()) == null)
334                 return r.getString(R.string.account_name_is_empty);
335             if (!amount.hasLiteralValue() &&
336                 (amount.getMatchGroup() < 1 || amount.getMatchGroup() > patternGroupCount))
337                 return r.getString(R.string.invalid_matching_group_number);
338
339             return null;
340         }
341         public boolean hasLiteralAccountComment() {
342             return accountComment.hasLiteralValue();
343         }
344         public boolean equalContents(AccountRow o) {
345             return amount.equals(o.amount) && accountName.equals(o.accountName) &&
346                    accountComment.equals(o.accountComment);
347         }
348         public void switchToLiteralAmount() {
349             amount.switchToLiteral();
350         }
351         public void switchToLiteralAccountName() {
352             accountName.switchToLiteral();
353         }
354         public void switchToLiteralAccountComment() {
355             accountComment.switchToLiteral();
356         }
357         public PatternAccount toDBO(@NonNull Long patternId) {
358             PatternAccount result = new PatternAccount(id, patternId, position);
359
360             if (accountName.hasLiteralValue())
361                 result.setAccountName(accountName.getValue());
362             else
363                 result.setAccountNameMatchGroup(accountName.getMatchGroup());
364
365             if (accountComment.hasLiteralValue())
366                 result.setAccountComment(accountComment.getValue());
367             else
368                 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
369
370             if (amount.hasLiteralValue())
371                 result.setAmount(amount.getValue());
372             else
373                 result.setAmountMatchGroup(amount.getMatchGroup());
374
375             return result;
376         }
377     }
378
379     public static class Header extends PatternDetailsItem {
380         private String pattern = "";
381         private String testText = "";
382         private Pattern compiledPattern;
383         private String patternError;
384         private String name = "";
385         private PossiblyMatchedValue<String> transactionDescription =
386                 PossiblyMatchedValue.withLiteralString("");
387         private PossiblyMatchedValue<String> transactionComment =
388                 PossiblyMatchedValue.withLiteralString("");
389         private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
390         private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
391         private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
392         private Header() {
393             super(Type.HEADER);
394         }
395         public Header(Header origin) {
396             this();
397             id = origin.id;
398             name = origin.name;
399             testText = origin.testText;
400             setPattern(origin.pattern);
401
402             transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
403             transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
404
405             dateYear = new PossiblyMatchedValue<>(origin.dateYear);
406             dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
407             dateDay = new PossiblyMatchedValue<>(origin.dateDay);
408         }
409         public String getName() {
410             return name;
411         }
412         public void setName(String name) {
413             this.name = name;
414         }
415         public String getPattern() {
416             return pattern;
417         }
418         public void setPattern(String pattern) {
419             this.pattern = pattern;
420             if (pattern != null) {
421                 try {
422                     this.compiledPattern = Pattern.compile(pattern);
423                     this.patternError = null;
424                 }
425                 catch (PatternSyntaxException e) {
426                     this.compiledPattern = null;
427                     this.patternError = e.getMessage();
428                 }
429             }
430             else {
431                 patternError = "Missing pattern";
432             }
433         }
434         @NonNull
435         @Override
436         public String toString() {
437             return super.toString() +
438                    String.format(" name[%s] pat[%s] test[%s] tran[%s] com[%s]", name, pattern,
439                            testText, transactionDescription, transactionComment);
440         }
441         public String getTestText() {
442             return testText;
443         }
444         public void setTestText(String testText) {
445             this.testText = testText;
446         }
447         public String getTransactionDescription() {
448             return transactionDescription.getValue();
449         }
450         public void setTransactionDescription(String transactionDescription) {
451             this.transactionDescription.setValue(transactionDescription);
452         }
453         public String getTransactionComment() {
454             return transactionComment.getValue();
455         }
456         public void setTransactionComment(String transactionComment) {
457             this.transactionComment.setValue(transactionComment);
458         }
459         public Integer getDateYear() {
460             return dateYear.getValue();
461         }
462         public void setDateYear(Integer dateYear) {
463             this.dateYear.setValue(dateYear);
464         }
465         public Integer getDateMonth() {
466             return dateMonth.getValue();
467         }
468         public void setDateMonth(Integer dateMonth) {
469             this.dateMonth.setValue(dateMonth);
470         }
471         public Integer getDateDay() {
472             return dateDay.getValue();
473         }
474         public void setDateDay(Integer dateDay) {
475             this.dateDay.setValue(dateDay);
476         }
477         public int getDateYearMatchGroup() {
478             return dateYear.getMatchGroup();
479         }
480         public void setDateYearMatchGroup(int dateYearMatchGroup) {
481             this.dateYear.setMatchGroup(dateYearMatchGroup);
482         }
483         public int getDateMonthMatchGroup() {
484             return dateMonth.getMatchGroup();
485         }
486         public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
487             this.dateMonth.setMatchGroup(dateMonthMatchGroup);
488         }
489         public int getDateDayMatchGroup() {
490             return dateDay.getMatchGroup();
491         }
492         public void setDateDayMatchGroup(int dateDayMatchGroup) {
493             this.dateDay.setMatchGroup(dateDayMatchGroup);
494         }
495         public boolean hasLiteralDateYear() {
496             return dateYear.hasLiteralValue();
497         }
498         public boolean hasLiteralDateMonth() {
499             return dateMonth.hasLiteralValue();
500         }
501         public boolean hasLiteralDateDay() {
502             return dateDay.hasLiteralValue();
503         }
504         public boolean hasLiteralTransactionDescription() { return transactionDescription.hasLiteralValue(); }
505         public boolean hasLiteralTransactionComment() { return transactionComment.hasLiteralValue(); }
506         public String getProblem(@NonNull Resources r, int patternGroupCount) {
507             if (patternError != null)
508                 return r.getString(R.string.pattern_has_errors) + ": " + patternError;
509             if (Misc.emptyIsNull(pattern) == null)
510                 return r.getString(R.string.pattern_is_empty);
511
512             if (!dateYear.hasLiteralValue() && compiledPattern != null &&
513                 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
514                 return r.getString(R.string.invalid_matching_group_number);
515
516             if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
517                 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
518                 return r.getString(R.string.invalid_matching_group_number);
519
520             if (!dateDay.hasLiteralValue() && compiledPattern != null &&
521                 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
522                 return r.getString(R.string.invalid_matching_group_number);
523
524             return null;
525         }
526
527         public boolean equalContents(Header o) {
528             if (!dateDay.equals(o.dateDay))
529                 return false;
530             if (!dateMonth.equals(o.dateMonth))
531                 return false;
532             if (!dateYear.equals(o.dateYear))
533                 return false;
534             if (!transactionDescription.equals(o.transactionDescription))
535                 return false;
536             if (!transactionComment.equals(o.transactionComment))
537                 return true;
538
539             return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
540                    Misc.equalStrings(testText, o.testText);
541         }
542         public String getMatchGroupText(int group) {
543             if (compiledPattern != null && testText != null) {
544                 Matcher m = compiledPattern.matcher(testText);
545                 if (m.matches())
546                     return m.group(group);
547             }
548
549             return "ø";
550         }
551         public Pattern getCompiledPattern() {
552             return compiledPattern;
553         }
554         public void switchToLiteralTransactionDescription() {
555             transactionDescription.switchToLiteral();
556         }
557         public void switchToLiteralTransactionComment() {
558             transactionComment.switchToLiteral();
559         }
560         public int getTransactionDescriptionMatchGroup() {
561             return transactionDescription.getMatchGroup();
562         }
563         public void setTransactionDescriptionMatchGroup(int group) {
564             transactionDescription.setMatchGroup(group);
565         }
566         public int getTransactionCommentMatchGroup() {
567             return transactionComment.getMatchGroup();
568         }
569         public void setTransactionCommentMatchGroup(int group) {
570             transactionComment.setMatchGroup(group);
571         }
572         public void switchToLiteralDateYear() {
573             dateYear.switchToLiteral();
574         }
575         public void switchToLiteralDateMonth() {
576             dateMonth.switchToLiteral();
577         }
578         public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
579         public PatternHeader toDBO() {
580             PatternHeader result = new PatternHeader(id, name, pattern);
581
582             if (Misc.emptyIsNull(testText) != null)
583                 result.setTestText(testText);
584
585             if (transactionDescription.hasLiteralValue())
586                 result.setTransactionDescription(transactionDescription.getValue());
587             else
588                 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
589
590             if (transactionComment.hasLiteralValue())
591                 result.setTransactionComment(transactionComment.getValue());
592             else
593                 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
594
595             if (dateYear.hasLiteralValue())
596                 result.setDateYear(dateYear.getValue());
597             else
598                 result.setDateYearMatchGroup(dateYear.getMatchGroup());
599
600             if (dateMonth.hasLiteralValue())
601                 result.setDateMonth(dateMonth.getValue());
602             else
603                 result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
604
605             if (dateDay.hasLiteralValue())
606                 result.setDateDay(dateDay.getValue());
607             else
608                 result.setDateDayMatchGroup(dateDay.getMatchGroup());
609
610             return result;
611         }
612     }
613 }