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