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());
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());
72 else if (p instanceof PatternAccount) {
73 PatternAccount pa = (PatternAccount) p;
74 AccountRow acc = createAccountRow();
75 acc.setId(pa.getId());
77 if (pa.getAccountNameMatchGroup() == null)
78 acc.setAccountName(Misc.nullIsEmpty(pa.getAccountName()));
80 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
82 if (pa.getAccountCommentMatchGroup() == null)
83 acc.setAccountComment(Misc.nullIsEmpty(pa.getAccountComment()));
85 acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
87 if (pa.getCurrencyMatchGroup() == null) {
88 final Integer currencyId = pa.getCurrency();
89 if (currencyId != null && currencyId > 0)
90 acc.setCurrency(Currency.loadById(currencyId));
93 acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
95 final Integer amountMatchGroup = pa.getAmountMatchGroup();
96 if (amountMatchGroup != null && amountMatchGroup > 0)
97 acc.setAmountMatchGroup(amountMatchGroup);
99 acc.setAmount(pa.getAmount());
104 throw new IllegalStateException("Unexpected item class " + p.getClass());
107 public Header asHeaderItem() {
108 ensureType(Type.HEADER);
109 return (Header) this;
111 public AccountRow asAccountRowItem() {
112 ensureType(Type.ACCOUNT_ITEM);
113 return (AccountRow) this;
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(),
121 void ensureTrue(boolean flag) {
123 throw new IllegalStateException(
124 "Literal value requested, but it is matched via a pattern group");
126 void ensureFalse(boolean flag) {
128 throw new IllegalStateException("Matching group requested, but the value is a literal");
130 public long getId() {
133 public void setId(Long id) {
136 public void setId(int id) {
139 public long getPosition() {
142 public void setPosition(Long position) {
143 this.position = position;
145 abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
146 public Type getType() {
150 HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
160 static class PossiblyMatchedValue<T> {
161 private boolean literalValue;
163 private int matchGroup;
164 public PossiblyMatchedValue() {
168 public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
169 literalValue = origin.literalValue;
170 value = origin.value;
171 matchGroup = origin.matchGroup;
174 public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
175 PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
176 result.setValue(initialValue);
180 public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
181 PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
182 result.setValue(initialValue);
185 public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
186 PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
187 result.setValue(initialValue);
191 public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
192 PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
193 result.setValue(initialValue);
196 public T getValue() {
198 throw new IllegalStateException("Value is not literal");
201 public void setValue(T newValue) {
205 public boolean hasLiteralValue() {
208 public int getMatchGroup() {
210 throw new IllegalStateException("Value is literal");
213 public void setMatchGroup(int group) {
214 this.matchGroup = group;
215 literalValue = false;
217 public boolean equals(PossiblyMatchedValue<T> other) {
218 if (!other.literalValue == literalValue)
222 return other.value == null;
223 return value.equals(other.value);
226 return matchGroup == other.matchGroup;
228 public void switchToLiteral() {
233 public static class TYPE {
234 public static final int header = 0;
235 public static final int accountItem = 1;
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);
249 public int getAccountCommentMatchGroup() {
250 return accountComment.getMatchGroup();
252 public void setAccountCommentMatchGroup(int group) {
253 accountComment.setMatchGroup(group);
255 public String getAccountComment() {
256 return accountComment.getValue();
258 public void setAccountComment(String comment) {
259 this.accountComment.setValue(comment);
261 public int getCurrencyMatchGroup() {
262 return currency.getMatchGroup();
264 public void setCurrencyMatchGroup(int group) {
265 currency.setMatchGroup(group);
267 public Currency getCurrency() {
268 return currency.getValue();
270 public void setCurrency(Currency currency) {
271 this.currency.setValue(currency);
273 public int getAccountNameMatchGroup() {
274 return accountName.getMatchGroup();
276 public void setAccountNameMatchGroup(int group) {
277 accountName.setMatchGroup(group);
279 public String getAccountName() {
280 return accountName.getValue();
282 public void setAccountName(String accountName) {
283 this.accountName.setValue(accountName);
285 public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
286 public boolean hasLiteralAmount() {
287 return amount.hasLiteralValue();
289 public int getAmountMatchGroup() {
290 return amount.getMatchGroup();
292 public void setAmountMatchGroup(int group) {
293 amount.setMatchGroup(group);
295 public Float getAmount() {
296 return amount.getValue();
298 public void setAmount(Float amount) {
299 this.amount.setValue(amount);
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);
310 public boolean hasLiteralAccountComment() {
311 return accountComment.hasLiteralValue();
313 public boolean equalContents(AccountRow o) {
314 return amount.equals(o.amount) && accountName.equals(o.accountName) &&
315 accountComment.equals(o.accountComment);
317 public void switchToLiteralAmount() {
318 amount.switchToLiteral();
320 public void switchToLiteralAccountName() {
321 accountName.switchToLiteral();
323 public void switchToLiteralAccountComment() {
324 accountComment.switchToLiteral();
326 public PatternAccount toDBO(@NonNull Long patternId) {
327 PatternAccount result = new PatternAccount(id, patternId, position);
329 if (accountName.hasLiteralValue())
330 result.setAccountName(accountName.getValue());
332 result.setAccountNameMatchGroup(accountName.getMatchGroup());
334 if (accountComment.hasLiteralValue())
335 result.setAccountComment(accountComment.getValue());
337 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
339 if (amount.hasLiteralValue())
340 result.setAmount(amount.getValue());
342 result.setAmountMatchGroup(amount.getMatchGroup());
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);
364 public Header(Header origin) {
368 testText = origin.testText;
369 setPattern(origin.pattern);
371 transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
372 transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
374 dateYear = new PossiblyMatchedValue<>(origin.dateYear);
375 dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
376 dateDay = new PossiblyMatchedValue<>(origin.dateDay);
378 public String getName() {
381 public void setName(String name) {
384 public String getPattern() {
387 public void setPattern(String pattern) {
388 this.pattern = pattern;
389 if (pattern != null) {
391 this.compiledPattern = Pattern.compile(pattern);
392 this.patternError = null;
394 catch (PatternSyntaxException e) {
395 this.compiledPattern = null;
396 this.patternError = e.getMessage();
400 patternError = "Missing pattern";
405 public String toString() {
406 return super.toString() +
407 String.format(" name[%s] pat[%s] test[%s]", name, pattern, testText);
409 public String getTestText() {
412 public void setTestText(String testText) {
413 this.testText = testText;
415 public String getTransactionDescription() {
416 return transactionDescription.getValue();
418 public void setTransactionDescription(String transactionDescription) {
419 this.transactionDescription.setValue(transactionDescription);
421 public String getTransactionComment() {
422 return transactionComment.getValue();
424 public void setTransactionComment(String transactionComment) {
425 this.transactionComment.setValue(transactionComment);
427 public Integer getDateYear() {
428 return dateYear.getValue();
430 public void setDateYear(Integer dateYear) {
431 this.dateYear.setValue(dateYear);
433 public Integer getDateMonth() {
434 return dateMonth.getValue();
436 public void setDateMonth(Integer dateMonth) {
437 this.dateMonth.setValue(dateMonth);
439 public Integer getDateDay() {
440 return dateDay.getValue();
442 public void setDateDay(Integer dateDay) {
443 this.dateDay.setValue(dateDay);
445 public int getDateYearMatchGroup() {
446 return dateYear.getMatchGroup();
448 public void setDateYearMatchGroup(int dateYearMatchGroup) {
449 this.dateYear.setMatchGroup(dateYearMatchGroup);
451 public int getDateMonthMatchGroup() {
452 return dateMonth.getMatchGroup();
454 public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
455 this.dateMonth.setMatchGroup(dateMonthMatchGroup);
457 public int getDateDayMatchGroup() {
458 return dateDay.getMatchGroup();
460 public void setDateDayMatchGroup(int dateDayMatchGroup) {
461 this.dateDay.setMatchGroup(dateDayMatchGroup);
463 public boolean hasLiteralDateYear() {
464 return dateYear.hasLiteralValue();
466 public boolean hasLiteralDateMonth() {
467 return dateMonth.hasLiteralValue();
469 public boolean hasLiteralDateDay() {
470 return dateDay.hasLiteralValue();
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);
480 if (!dateYear.hasLiteralValue() && compiledPattern != null &&
481 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
482 return r.getString(R.string.invalid_matching_group_number);
484 if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
485 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
486 return r.getString(R.string.invalid_matching_group_number);
488 if (!dateDay.hasLiteralValue() && compiledPattern != null &&
489 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
490 return r.getString(R.string.invalid_matching_group_number);
495 public boolean equalContents(Header o) {
496 if (!dateDay.equals(o.dateDay))
498 if (!dateMonth.equals(o.dateMonth))
500 if (!dateYear.equals(o.dateYear))
502 if (!transactionDescription.equals(o.transactionDescription))
504 if (!transactionComment.equals(o.transactionComment))
507 return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
508 Misc.equalStrings(testText, o.testText);
510 public String getMatchGroupText(int group) {
511 if (compiledPattern != null && testText != null) {
512 Matcher m = compiledPattern.matcher(testText);
514 return m.group(group);
519 public Pattern getCompiledPattern() {
520 return compiledPattern;
522 public void switchToLiteralTransactionDescription() {
523 transactionDescription.switchToLiteral();
525 public void switchToLiteralTransactionComment() {
526 transactionComment.switchToLiteral();
528 public int getTransactionDescriptionMatchGroup() {
529 return transactionDescription.getMatchGroup();
531 public void setTransactionDescriptionMatchGroup(short group) {
532 transactionDescription.setMatchGroup(group);
534 public int getTransactionCommentMatchGroup() {
535 return transactionComment.getMatchGroup();
537 public void setTransactionCommentMatchGroup(short group) {
538 transactionComment.setMatchGroup(group);
540 public void switchToLiteralDateYear() {
541 dateYear.switchToLiteral();
543 public void switchToLiteralDateMonth() {
544 dateMonth.switchToLiteral();
546 public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
547 public PatternHeader toDBO() {
548 PatternHeader result = new PatternHeader(id, name, pattern);
550 if (Misc.emptyIsNull(testText) != null)
551 result.setTestText(testText);
553 if (transactionDescription.hasLiteralValue())
554 result.setTransactionDescription(transactionDescription.getValue());
556 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
558 if (transactionComment.hasLiteralValue())
559 result.setTransactionComment(transactionComment.getValue());
561 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
563 if (dateYear.hasLiteralValue())
564 result.setDateYear(dateYear.getValue());
566 result.setDateYearMatchGroup(dateYear.getMatchGroup());
568 if (dateMonth.hasLiteralValue())
569 result.setDateMonth(dateMonth.getValue());
571 result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
573 if (dateDay.hasLiteralValue())
574 result.setDateDay(dateDay.getValue());
576 result.setDateDayMatchGroup(dateDay.getMatchGroup());