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.
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.
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/>.
18 package net.ktnx.mobileledger.model;
20 import android.content.res.Resources;
22 import androidx.annotation.NonNull;
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;
30 import org.jetbrains.annotations.Contract;
31 import org.jetbrains.annotations.NotNull;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 import java.util.regex.PatternSyntaxException;
37 abstract public class PatternDetailsItem {
38 private final Type type;
40 protected Long position;
42 protected PatternDetailsItem(Type type) {
46 public static @NotNull PatternDetailsItem.Header createHeader() {
49 public static @NotNull PatternDetailsItem.Header createHeader(Header origin) {
50 return new Header(origin);
53 public static @NotNull PatternDetailsItem.AccountRow createAccountRow() {
54 return new AccountRow();
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());
65 if (ph.getTransactionDescriptionMatchGroup() == null)
66 header.setTransactionDescription(ph.getTransactionDescription());
68 header.setTransactionDescriptionMatchGroup(ph.getTransactionDescriptionMatchGroup());
70 if (ph.getTransactionCommentMatchGroup() == null)
71 header.setTransactionComment(ph.getTransactionComment());
73 header.setTransactionCommentMatchGroup(ph.getTransactionCommentMatchGroup());
75 header.setDateDayMatchGroup(ph.getDateDayMatchGroup());
76 header.setDateMonthMatchGroup(ph.getDateMonthMatchGroup());
77 header.setDateYearMatchGroup(ph.getDateYearMatchGroup());
81 else if (p instanceof PatternAccount) {
82 PatternAccount pa = (PatternAccount) p;
83 AccountRow acc = createAccountRow();
84 acc.setId(pa.getId());
86 if (pa.getAccountNameMatchGroup() == null)
87 acc.setAccountName(Misc.nullIsEmpty(pa.getAccountName()));
89 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
91 if (pa.getAccountCommentMatchGroup() == null)
92 acc.setAccountComment(Misc.nullIsEmpty(pa.getAccountComment()));
94 acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
96 if (pa.getCurrencyMatchGroup() == null) {
97 final Integer currencyId = pa.getCurrency();
98 if (currencyId != null && currencyId > 0)
99 acc.setCurrency(Currency.loadById(currencyId));
102 acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
104 final Integer amountMatchGroup = pa.getAmountMatchGroup();
105 if (amountMatchGroup != null && amountMatchGroup > 0)
106 acc.setAmountMatchGroup(amountMatchGroup);
108 acc.setAmount(pa.getAmount());
113 throw new IllegalStateException("Unexpected item class " + p.getClass());
116 public Header asHeaderItem() {
117 ensureType(Type.HEADER);
118 return (Header) this;
120 public AccountRow asAccountRowItem() {
121 ensureType(Type.ACCOUNT_ITEM);
122 return (AccountRow) this;
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(),
130 void ensureTrue(boolean flag) {
132 throw new IllegalStateException(
133 "Literal value requested, but it is matched via a pattern group");
135 void ensureFalse(boolean flag) {
137 throw new IllegalStateException("Matching group requested, but the value is a literal");
139 public long getId() {
142 public void setId(Long id) {
145 public void setId(int id) {
148 public long getPosition() {
151 public void setPosition(Long position) {
152 this.position = position;
154 abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
155 public Type getType() {
159 HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
169 static class PossiblyMatchedValue<T> {
170 private boolean literalValue;
172 private int matchGroup;
173 public PossiblyMatchedValue() {
177 public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
178 literalValue = origin.literalValue;
179 value = origin.value;
180 matchGroup = origin.matchGroup;
183 public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
184 PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
185 result.setValue(initialValue);
189 public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
190 PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
191 result.setValue(initialValue);
194 public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
195 PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
196 result.setValue(initialValue);
200 public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
201 PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
202 result.setValue(initialValue);
205 public T getValue() {
207 throw new IllegalStateException("Value is not literal");
210 public void setValue(T newValue) {
214 public boolean hasLiteralValue() {
217 public int getMatchGroup() {
219 throw new IllegalStateException("Value is literal");
222 public void setMatchGroup(int group) {
223 this.matchGroup = group;
224 literalValue = false;
226 public boolean equals(PossiblyMatchedValue<T> other) {
227 if (!other.literalValue == literalValue)
231 return other.value == null;
232 return value.equals(other.value);
235 return matchGroup == other.matchGroup;
237 public void switchToLiteral() {
242 public static class TYPE {
243 public static final int header = 0;
244 public static final int accountItem = 1;
247 public static class AccountRow extends PatternDetailsItem {
248 private final PossiblyMatchedValue<String> accountName =
249 PossiblyMatchedValue.withLiteralString("");
250 private final PossiblyMatchedValue<String> accountComment =
251 PossiblyMatchedValue.withLiteralString("");
252 private final PossiblyMatchedValue<Float> amount =
253 PossiblyMatchedValue.withLiteralFloat(0f);
254 private final PossiblyMatchedValue<Currency> currency = new PossiblyMatchedValue<>();
255 private AccountRow() {
256 super(Type.ACCOUNT_ITEM);
258 public int getAccountCommentMatchGroup() {
259 return accountComment.getMatchGroup();
261 public void setAccountCommentMatchGroup(int group) {
262 accountComment.setMatchGroup(group);
264 public String getAccountComment() {
265 return accountComment.getValue();
267 public void setAccountComment(String comment) {
268 this.accountComment.setValue(comment);
270 public int getCurrencyMatchGroup() {
271 return currency.getMatchGroup();
273 public void setCurrencyMatchGroup(int group) {
274 currency.setMatchGroup(group);
276 public Currency getCurrency() {
277 return currency.getValue();
279 public void setCurrency(Currency currency) {
280 this.currency.setValue(currency);
282 public int getAccountNameMatchGroup() {
283 return accountName.getMatchGroup();
285 public void setAccountNameMatchGroup(int group) {
286 accountName.setMatchGroup(group);
288 public String getAccountName() {
289 return accountName.getValue();
291 public void setAccountName(String accountName) {
292 this.accountName.setValue(accountName);
294 public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
295 public boolean hasLiteralAmount() {
296 return amount.hasLiteralValue();
298 public int getAmountMatchGroup() {
299 return amount.getMatchGroup();
301 public void setAmountMatchGroup(int group) {
302 amount.setMatchGroup(group);
304 public Float getAmount() {
305 return amount.getValue();
307 public void setAmount(Float amount) {
308 this.amount.setValue(amount);
310 public String getProblem(@NonNull Resources r, int patternGroupCount) {
311 if (Misc.emptyIsNull(accountName.getValue()) == null)
312 return r.getString(R.string.account_name_is_empty);
313 if (!amount.hasLiteralValue() &&
314 (amount.getMatchGroup() < 1 || amount.getMatchGroup() > patternGroupCount))
315 return r.getString(R.string.invalid_matching_group_number);
319 public boolean hasLiteralAccountComment() {
320 return accountComment.hasLiteralValue();
322 public boolean equalContents(AccountRow o) {
323 return amount.equals(o.amount) && accountName.equals(o.accountName) &&
324 accountComment.equals(o.accountComment);
326 public void switchToLiteralAmount() {
327 amount.switchToLiteral();
329 public void switchToLiteralAccountName() {
330 accountName.switchToLiteral();
332 public void switchToLiteralAccountComment() {
333 accountComment.switchToLiteral();
335 public PatternAccount toDBO(@NonNull Long patternId) {
336 PatternAccount result = new PatternAccount(id, patternId, position);
338 if (accountName.hasLiteralValue())
339 result.setAccountName(accountName.getValue());
341 result.setAccountNameMatchGroup(accountName.getMatchGroup());
343 if (accountComment.hasLiteralValue())
344 result.setAccountComment(accountComment.getValue());
346 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
348 if (amount.hasLiteralValue())
349 result.setAmount(amount.getValue());
351 result.setAmountMatchGroup(amount.getMatchGroup());
357 public static class Header extends PatternDetailsItem {
358 private String pattern = "";
359 private String testText = "";
360 private Pattern compiledPattern;
361 private String patternError;
362 private String name = "";
363 private PossiblyMatchedValue<String> transactionDescription =
364 PossiblyMatchedValue.withLiteralString("");
365 private PossiblyMatchedValue<String> transactionComment =
366 PossiblyMatchedValue.withLiteralString("");
367 private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
368 private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
369 private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
373 public Header(Header origin) {
377 testText = origin.testText;
378 setPattern(origin.pattern);
380 transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
381 transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
383 dateYear = new PossiblyMatchedValue<>(origin.dateYear);
384 dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
385 dateDay = new PossiblyMatchedValue<>(origin.dateDay);
387 public String getName() {
390 public void setName(String name) {
393 public String getPattern() {
396 public void setPattern(String pattern) {
397 this.pattern = pattern;
398 if (pattern != null) {
400 this.compiledPattern = Pattern.compile(pattern);
401 this.patternError = null;
403 catch (PatternSyntaxException e) {
404 this.compiledPattern = null;
405 this.patternError = e.getMessage();
409 patternError = "Missing pattern";
414 public String toString() {
415 return super.toString() +
416 String.format(" name[%s] pat[%s] test[%s]", name, pattern, testText);
418 public String getTestText() {
421 public void setTestText(String testText) {
422 this.testText = testText;
424 public String getTransactionDescription() {
425 return transactionDescription.getValue();
427 public void setTransactionDescription(String transactionDescription) {
428 this.transactionDescription.setValue(transactionDescription);
430 public String getTransactionComment() {
431 return transactionComment.getValue();
433 public void setTransactionComment(String transactionComment) {
434 this.transactionComment.setValue(transactionComment);
436 public Integer getDateYear() {
437 return dateYear.getValue();
439 public void setDateYear(Integer dateYear) {
440 this.dateYear.setValue(dateYear);
442 public Integer getDateMonth() {
443 return dateMonth.getValue();
445 public void setDateMonth(Integer dateMonth) {
446 this.dateMonth.setValue(dateMonth);
448 public Integer getDateDay() {
449 return dateDay.getValue();
451 public void setDateDay(Integer dateDay) {
452 this.dateDay.setValue(dateDay);
454 public int getDateYearMatchGroup() {
455 return dateYear.getMatchGroup();
457 public void setDateYearMatchGroup(int dateYearMatchGroup) {
458 this.dateYear.setMatchGroup(dateYearMatchGroup);
460 public int getDateMonthMatchGroup() {
461 return dateMonth.getMatchGroup();
463 public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
464 this.dateMonth.setMatchGroup(dateMonthMatchGroup);
466 public int getDateDayMatchGroup() {
467 return dateDay.getMatchGroup();
469 public void setDateDayMatchGroup(int dateDayMatchGroup) {
470 this.dateDay.setMatchGroup(dateDayMatchGroup);
472 public boolean hasLiteralDateYear() {
473 return dateYear.hasLiteralValue();
475 public boolean hasLiteralDateMonth() {
476 return dateMonth.hasLiteralValue();
478 public boolean hasLiteralDateDay() {
479 return dateDay.hasLiteralValue();
481 public boolean hasLiteralTransactionDescription() { return transactionDescription.hasLiteralValue(); }
482 public boolean hasLiteralTransactionComment() { return transactionComment.hasLiteralValue(); }
483 public String getProblem(@NonNull Resources r, int patternGroupCount) {
484 if (patternError != null)
485 return r.getString(R.string.pattern_has_errors) + ": " + patternError;
486 if (Misc.emptyIsNull(pattern) == null)
487 return r.getString(R.string.pattern_is_empty);
489 if (!dateYear.hasLiteralValue() && compiledPattern != null &&
490 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
491 return r.getString(R.string.invalid_matching_group_number);
493 if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
494 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
495 return r.getString(R.string.invalid_matching_group_number);
497 if (!dateDay.hasLiteralValue() && compiledPattern != null &&
498 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
499 return r.getString(R.string.invalid_matching_group_number);
504 public boolean equalContents(Header o) {
505 if (!dateDay.equals(o.dateDay))
507 if (!dateMonth.equals(o.dateMonth))
509 if (!dateYear.equals(o.dateYear))
511 if (!transactionDescription.equals(o.transactionDescription))
513 if (!transactionComment.equals(o.transactionComment))
516 return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
517 Misc.equalStrings(testText, o.testText);
519 public String getMatchGroupText(int group) {
520 if (compiledPattern != null && testText != null) {
521 Matcher m = compiledPattern.matcher(testText);
523 return m.group(group);
528 public Pattern getCompiledPattern() {
529 return compiledPattern;
531 public void switchToLiteralTransactionDescription() {
532 transactionDescription.switchToLiteral();
534 public void switchToLiteralTransactionComment() {
535 transactionComment.switchToLiteral();
537 public int getTransactionDescriptionMatchGroup() {
538 return transactionDescription.getMatchGroup();
540 public void setTransactionDescriptionMatchGroup(int group) {
541 transactionDescription.setMatchGroup(group);
543 public int getTransactionCommentMatchGroup() {
544 return transactionComment.getMatchGroup();
546 public void setTransactionCommentMatchGroup(int group) {
547 transactionComment.setMatchGroup(group);
549 public void switchToLiteralDateYear() {
550 dateYear.switchToLiteral();
552 public void switchToLiteralDateMonth() {
553 dateMonth.switchToLiteral();
555 public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
556 public PatternHeader toDBO() {
557 PatternHeader result = new PatternHeader(id, name, pattern);
559 if (Misc.emptyIsNull(testText) != null)
560 result.setTestText(testText);
562 if (transactionDescription.hasLiteralValue())
563 result.setTransactionDescription(transactionDescription.getValue());
565 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
567 if (transactionComment.hasLiteralValue())
568 result.setTransactionComment(transactionComment.getValue());
570 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
572 if (dateYear.hasLiteralValue())
573 result.setDateYear(dateYear.getValue());
575 result.setDateYearMatchGroup(dateYear.getMatchGroup());
577 if (dateMonth.hasLiteralValue())
578 result.setDateMonth(dateMonth.getValue());
580 result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
582 if (dateDay.hasLiteralValue())
583 result.setDateDay(dateDay.getValue());
585 result.setDateDayMatchGroup(dateDay.getMatchGroup());