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