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