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.TemplateAccount;
26 import net.ktnx.mobileledger.db.TemplateBase;
27 import net.ktnx.mobileledger.db.TemplateHeader;
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 TemplateDetailsItem {
38 private final Type type;
40 protected Long position;
42 protected TemplateDetailsItem(Type type) {
46 public static @NotNull TemplateDetailsItem.Header createHeader() {
49 public static @NotNull TemplateDetailsItem.Header createHeader(Header origin) {
50 return new Header(origin);
53 public static @NotNull TemplateDetailsItem.AccountRow createAccountRow() {
54 return new AccountRow();
56 public static TemplateDetailsItem fromRoomObject(TemplateBase p) {
57 if (p instanceof TemplateHeader) {
58 TemplateHeader ph = (TemplateHeader) 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(
69 ph.getTransactionDescriptionMatchGroup());
71 if (ph.getTransactionCommentMatchGroup() == null)
72 header.setTransactionComment(ph.getTransactionComment());
74 header.setTransactionCommentMatchGroup(ph.getTransactionCommentMatchGroup());
76 if (ph.getDateDayMatchGroup() == null)
77 header.setDateDay(ph.getDateDay());
79 header.setDateDayMatchGroup(ph.getDateDayMatchGroup());
81 if (ph.getDateMonthMatchGroup() == null)
82 header.setDateMonth(ph.getDateMonth());
84 header.setDateMonthMatchGroup(ph.getDateMonthMatchGroup());
86 if (ph.getDateYearMatchGroup() == null)
87 header.setDateYear(ph.getDateYear());
89 header.setDateYearMatchGroup(ph.getDateYearMatchGroup());
93 else if (p instanceof TemplateAccount) {
94 TemplateAccount pa = (TemplateAccount) p;
95 AccountRow acc = createAccountRow();
96 acc.setId(pa.getId());
98 if (pa.getAccountNameMatchGroup() == null)
99 acc.setAccountName(Misc.nullIsEmpty(pa.getAccountName()));
101 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
103 if (pa.getAccountCommentMatchGroup() == null)
104 acc.setAccountComment(Misc.nullIsEmpty(pa.getAccountComment()));
106 acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
108 if (pa.getCurrencyMatchGroup() == null) {
109 final Integer currencyId = pa.getCurrency();
110 if (currencyId != null && currencyId > 0)
111 acc.setCurrency(Currency.loadById(currencyId));
114 acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
116 final Integer amountMatchGroup = pa.getAmountMatchGroup();
117 if (amountMatchGroup != null && amountMatchGroup > 0) {
118 acc.setAmountMatchGroup(amountMatchGroup);
119 final Boolean negateAmount = pa.getNegateAmount();
120 acc.setNegateAmount(negateAmount != null && negateAmount);
123 acc.setAmount(pa.getAmount());
128 throw new IllegalStateException("Unexpected item class " + p.getClass());
131 public Header asHeaderItem() {
132 ensureType(Type.HEADER);
133 return (Header) this;
135 public AccountRow asAccountRowItem() {
136 ensureType(Type.ACCOUNT_ITEM);
137 return (AccountRow) this;
139 private void ensureType(Type type) {
140 if (this.type != type)
141 throw new IllegalStateException(
142 String.format("Type is %s, but %s is required", this.type.toString(),
145 void ensureTrue(boolean flag) {
147 throw new IllegalStateException(
148 "Literal value requested, but it is matched via a pattern group");
150 void ensureFalse(boolean flag) {
152 throw new IllegalStateException("Matching group requested, but the value is a literal");
154 public long getId() {
157 public void setId(Long id) {
160 public void setId(int id) {
163 public long getPosition() {
166 public void setPosition(Long position) {
167 this.position = position;
169 abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
170 public Type getType() {
174 HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
184 static class PossiblyMatchedValue<T> {
185 private boolean literalValue;
187 private int matchGroup;
188 public PossiblyMatchedValue() {
192 public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
193 literalValue = origin.literalValue;
194 value = origin.value;
195 matchGroup = origin.matchGroup;
198 public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
199 PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
200 result.setValue(initialValue);
204 public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
205 PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
206 result.setValue(initialValue);
209 public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
210 PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
211 result.setValue(initialValue);
215 public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
216 PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
217 result.setValue(initialValue);
220 public T getValue() {
222 throw new IllegalStateException("Value is not literal");
225 public void setValue(T newValue) {
229 public boolean hasLiteralValue() {
232 public int getMatchGroup() {
234 throw new IllegalStateException("Value is literal");
237 public void setMatchGroup(int group) {
238 this.matchGroup = group;
239 literalValue = false;
241 public boolean equals(PossiblyMatchedValue<T> other) {
242 if (!other.literalValue == literalValue)
246 return other.value == null;
247 return value.equals(other.value);
250 return matchGroup == other.matchGroup;
252 public void switchToLiteral() {
255 public String toString() {
260 return value.toString();
262 return "grp:" + matchGroup;
267 public static class TYPE {
268 public static final int header = 0;
269 public static final int accountItem = 1;
272 public static class AccountRow extends TemplateDetailsItem {
273 private final PossiblyMatchedValue<String> accountName =
274 PossiblyMatchedValue.withLiteralString("");
275 private final PossiblyMatchedValue<String> accountComment =
276 PossiblyMatchedValue.withLiteralString("");
277 private final PossiblyMatchedValue<Float> amount =
278 PossiblyMatchedValue.withLiteralFloat(0f);
279 private final PossiblyMatchedValue<Currency> currency = new PossiblyMatchedValue<>();
280 private boolean negateAmount;
281 private AccountRow() {
282 super(Type.ACCOUNT_ITEM);
284 public boolean isNegateAmount() {
287 public void setNegateAmount(boolean negateAmount) {
288 this.negateAmount = negateAmount;
290 public int getAccountCommentMatchGroup() {
291 return accountComment.getMatchGroup();
293 public void setAccountCommentMatchGroup(int group) {
294 accountComment.setMatchGroup(group);
296 public String getAccountComment() {
297 return accountComment.getValue();
299 public void setAccountComment(String comment) {
300 this.accountComment.setValue(comment);
302 public int getCurrencyMatchGroup() {
303 return currency.getMatchGroup();
305 public void setCurrencyMatchGroup(int group) {
306 currency.setMatchGroup(group);
308 public Currency getCurrency() {
309 return currency.getValue();
311 public void setCurrency(Currency currency) {
312 this.currency.setValue(currency);
314 public int getAccountNameMatchGroup() {
315 return accountName.getMatchGroup();
317 public void setAccountNameMatchGroup(int group) {
318 accountName.setMatchGroup(group);
320 public String getAccountName() {
321 return accountName.getValue();
323 public void setAccountName(String accountName) {
324 this.accountName.setValue(accountName);
326 public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
327 public boolean hasLiteralAmount() {
328 return amount.hasLiteralValue();
330 public int getAmountMatchGroup() {
331 return amount.getMatchGroup();
333 public void setAmountMatchGroup(int group) {
334 amount.setMatchGroup(group);
336 public Float getAmount() {
337 return amount.getValue();
339 public void setAmount(Float amount) {
340 this.amount.setValue(amount);
342 public String getProblem(@NonNull Resources r, int patternGroupCount) {
343 if (Misc.emptyIsNull(accountName.getValue()) == null)
344 return r.getString(R.string.account_name_is_empty);
345 if (!amount.hasLiteralValue() &&
346 (amount.getMatchGroup() < 1 || amount.getMatchGroup() > patternGroupCount))
347 return r.getString(R.string.invalid_matching_group_number);
351 public boolean hasLiteralAccountComment() {
352 return accountComment.hasLiteralValue();
354 public boolean equalContents(AccountRow o) {
355 return amount.equals(o.amount) && accountName.equals(o.accountName) &&
356 accountComment.equals(o.accountComment) && negateAmount == o.negateAmount;
358 public void switchToLiteralAmount() {
359 amount.switchToLiteral();
361 public void switchToLiteralAccountName() {
362 accountName.switchToLiteral();
364 public void switchToLiteralAccountComment() {
365 accountComment.switchToLiteral();
367 public TemplateAccount toDBO(@NonNull Long patternId) {
368 TemplateAccount result = new TemplateAccount(id, patternId, position);
370 if (accountName.hasLiteralValue())
371 result.setAccountName(accountName.getValue());
373 result.setAccountNameMatchGroup(accountName.getMatchGroup());
375 if (accountComment.hasLiteralValue())
376 result.setAccountComment(accountComment.getValue());
378 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
380 if (amount.hasLiteralValue()) {
381 result.setAmount(amount.getValue());
382 result.setNegateAmount(null);
385 result.setAmountMatchGroup(amount.getMatchGroup());
386 result.setNegateAmount(negateAmount ? true : null);
393 public static class Header extends TemplateDetailsItem {
394 private String pattern = "";
395 private String testText = "";
396 private Pattern compiledPattern;
397 private String patternError;
398 private String name = "";
399 private PossiblyMatchedValue<String> transactionDescription =
400 PossiblyMatchedValue.withLiteralString("");
401 private PossiblyMatchedValue<String> transactionComment =
402 PossiblyMatchedValue.withLiteralString("");
403 private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
404 private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
405 private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
409 public Header(Header origin) {
413 testText = origin.testText;
414 setPattern(origin.pattern);
416 transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
417 transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
419 dateYear = new PossiblyMatchedValue<>(origin.dateYear);
420 dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
421 dateDay = new PossiblyMatchedValue<>(origin.dateDay);
423 public String getName() {
426 public void setName(String name) {
429 public String getPattern() {
432 public void setPattern(String pattern) {
433 this.pattern = pattern;
434 if (pattern != null) {
436 this.compiledPattern = Pattern.compile(pattern);
437 this.patternError = null;
439 catch (PatternSyntaxException e) {
440 this.compiledPattern = null;
441 this.patternError = e.getMessage();
445 patternError = "Missing pattern";
450 public String toString() {
451 return super.toString() +
452 String.format(" name[%s] pat[%s] test[%s] tran[%s] com[%s]", name, pattern,
453 testText, transactionDescription, transactionComment);
455 public String getTestText() {
458 public void setTestText(String testText) {
459 this.testText = testText;
461 public String getTransactionDescription() {
462 return transactionDescription.getValue();
464 public void setTransactionDescription(String transactionDescription) {
465 this.transactionDescription.setValue(transactionDescription);
467 public String getTransactionComment() {
468 return transactionComment.getValue();
470 public void setTransactionComment(String transactionComment) {
471 this.transactionComment.setValue(transactionComment);
473 public Integer getDateYear() {
474 return dateYear.getValue();
476 public void setDateYear(Integer dateYear) {
477 this.dateYear.setValue(dateYear);
479 public Integer getDateMonth() {
480 return dateMonth.getValue();
482 public void setDateMonth(Integer dateMonth) {
483 this.dateMonth.setValue(dateMonth);
485 public Integer getDateDay() {
486 return dateDay.getValue();
488 public void setDateDay(Integer dateDay) {
489 this.dateDay.setValue(dateDay);
491 public int getDateYearMatchGroup() {
492 return dateYear.getMatchGroup();
494 public void setDateYearMatchGroup(int dateYearMatchGroup) {
495 this.dateYear.setMatchGroup(dateYearMatchGroup);
497 public int getDateMonthMatchGroup() {
498 return dateMonth.getMatchGroup();
500 public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
501 this.dateMonth.setMatchGroup(dateMonthMatchGroup);
503 public int getDateDayMatchGroup() {
504 return dateDay.getMatchGroup();
506 public void setDateDayMatchGroup(int dateDayMatchGroup) {
507 this.dateDay.setMatchGroup(dateDayMatchGroup);
509 public boolean hasLiteralDateYear() {
510 return dateYear.hasLiteralValue();
512 public boolean hasLiteralDateMonth() {
513 return dateMonth.hasLiteralValue();
515 public boolean hasLiteralDateDay() {
516 return dateDay.hasLiteralValue();
518 public boolean hasLiteralTransactionDescription() { return transactionDescription.hasLiteralValue(); }
519 public boolean hasLiteralTransactionComment() { return transactionComment.hasLiteralValue(); }
520 public String getProblem(@NonNull Resources r, int patternGroupCount) {
521 if (patternError != null)
522 return r.getString(R.string.pattern_has_errors) + ": " + patternError;
523 if (Misc.emptyIsNull(pattern) == null)
524 return r.getString(R.string.pattern_is_empty);
526 if (!dateYear.hasLiteralValue() && compiledPattern != null &&
527 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
528 return r.getString(R.string.invalid_matching_group_number);
530 if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
531 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
532 return r.getString(R.string.invalid_matching_group_number);
534 if (!dateDay.hasLiteralValue() && compiledPattern != null &&
535 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
536 return r.getString(R.string.invalid_matching_group_number);
541 public boolean equalContents(Header o) {
542 if (!dateDay.equals(o.dateDay))
544 if (!dateMonth.equals(o.dateMonth))
546 if (!dateYear.equals(o.dateYear))
548 if (!transactionDescription.equals(o.transactionDescription))
550 if (!transactionComment.equals(o.transactionComment))
553 return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
554 Misc.equalStrings(testText, o.testText);
556 public String getMatchGroupText(int group) {
557 if (compiledPattern != null && testText != null) {
558 Matcher m = compiledPattern.matcher(testText);
560 return m.group(group);
565 public Pattern getCompiledPattern() {
566 return compiledPattern;
568 public void switchToLiteralTransactionDescription() {
569 transactionDescription.switchToLiteral();
571 public void switchToLiteralTransactionComment() {
572 transactionComment.switchToLiteral();
574 public int getTransactionDescriptionMatchGroup() {
575 return transactionDescription.getMatchGroup();
577 public void setTransactionDescriptionMatchGroup(int group) {
578 transactionDescription.setMatchGroup(group);
580 public int getTransactionCommentMatchGroup() {
581 return transactionComment.getMatchGroup();
583 public void setTransactionCommentMatchGroup(int group) {
584 transactionComment.setMatchGroup(group);
586 public void switchToLiteralDateYear() {
587 dateYear.switchToLiteral();
589 public void switchToLiteralDateMonth() {
590 dateMonth.switchToLiteral();
592 public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
593 public TemplateHeader toDBO() {
594 TemplateHeader result = new TemplateHeader(id, name, pattern);
596 if (Misc.emptyIsNull(testText) != null)
597 result.setTestText(testText);
599 if (transactionDescription.hasLiteralValue())
600 result.setTransactionDescription(transactionDescription.getValue());
602 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
604 if (transactionComment.hasLiteralValue())
605 result.setTransactionComment(transactionComment.getValue());
607 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
609 if (dateYear.hasLiteralValue())
610 result.setDateYear(dateYear.getValue());
612 result.setDateYearMatchGroup(dateYear.getMatchGroup());
614 if (dateMonth.hasLiteralValue())
615 result.setDateMonth(dateMonth.getValue());
617 result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
619 if (dateDay.hasLiteralValue())
620 result.setDateDay(dateDay.getValue());
622 result.setDateDayMatchGroup(dateDay.getMatchGroup());