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, long id, long position) {
44 this.id = (id <= 0) ? -position - 2 : id;
45 this.position = position;
48 public static @NotNull PatternDetailsItem.Header createHeader() {
51 public static @NotNull PatternDetailsItem.Header createHeader(Header origin) {
52 return new Header(origin);
55 public static @NotNull PatternDetailsItem.AccountRow createAccountRow(long position) {
56 return new AccountRow(-1, position);
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());
73 else if (p instanceof PatternAccount) {
74 PatternAccount pa = (PatternAccount) p;
75 AccountRow acc = createAccountRow(pa.getPosition());
77 if (Misc.emptyIsNull(pa.getAccountName()) != null)
78 acc.setAccountName(pa.getAccountName());
80 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
82 if (Misc.emptyIsNull(pa.getAccountComment()) == null)
83 acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
85 acc.setAccountComment(pa.getAccountComment());
87 if (pa.getCurrency() == null) {
88 acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
91 acc.setCurrency(Currency.loadById(pa.getCurrency()));
94 if (pa.getAmount() == null)
95 acc.setAmountMatchGroup(pa.getAmountMatchGroup());
97 acc.setAmount(pa.getAmount());
102 throw new IllegalStateException("Unexpected item class " + p.getClass());
105 public Header asHeaderItem() {
106 ensureType(Type.HEADER);
107 return (Header) this;
109 public AccountRow asAccountRowItem() {
110 ensureType(Type.ACCOUNT_ITEM);
111 return (AccountRow) this;
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(),
119 void ensureTrue(boolean flag) {
121 throw new IllegalStateException(
122 "Literal value requested, but it is matched via a pattern group");
124 void ensureFalse(boolean flag) {
126 throw new IllegalStateException("Matching group requested, but the value is a literal");
128 public long getId() {
131 public void setId(int id) {
134 public long getPosition() {
137 public void setPosition(int position) {
138 this.position = position;
140 abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
141 public Type getType() {
145 HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
155 static class PossiblyMatchedValue<T> {
156 private boolean literalValue;
158 private int matchGroup;
159 public PossiblyMatchedValue() {
163 public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
164 literalValue = origin.literalValue;
165 value = origin.value;
166 matchGroup = origin.matchGroup;
169 public static PossiblyMatchedValue<Integer> withLiteralInt(int initialValue) {
170 PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
171 result.setValue(initialValue);
175 public static PossiblyMatchedValue<Float> withLiteralFloat(float initialValue) {
176 PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
177 result.setValue(initialValue);
180 public static PossiblyMatchedValue<Short> withLiteralShort(short initialValue) {
181 PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
182 result.setValue(initialValue);
186 public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
187 PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
188 result.setValue(initialValue);
191 public T getValue() {
193 throw new IllegalStateException("Value is not literal");
196 public void setValue(T newValue) {
200 public boolean hasLiteralValue() {
203 public int getMatchGroup() {
205 throw new IllegalStateException("Value is literal");
208 public void setMatchGroup(int group) {
209 this.matchGroup = group;
210 literalValue = false;
212 public boolean equals(PossiblyMatchedValue<T> other) {
213 if (!other.literalValue == literalValue)
216 return value.equals(other.value);
218 return matchGroup == other.matchGroup;
220 public void switchToLiteral() {
225 public static class TYPE {
226 public static final int header = 0;
227 public static final int accountItem = 1;
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);
241 public int getAccountCommentMatchGroup() {
242 return accountComment.getMatchGroup();
244 public void setAccountCommentMatchGroup(int group) {
245 accountComment.setMatchGroup(group);
247 public String getAccountComment() {
248 return accountComment.getValue();
250 public void setAccountComment(String comment) {
251 this.accountComment.setValue(comment);
253 public int getCurrencyMatchGroup() {
254 return currency.getMatchGroup();
256 public void setCurrencyMatchGroup(int group) {
257 currency.setMatchGroup(group);
259 public Currency getCurrency() {
260 return currency.getValue();
262 public void setCurrency(Currency currency) {
263 this.currency.setValue(currency);
265 public int getAccountNameMatchGroup() {
266 return accountName.getMatchGroup();
268 public void setAccountNameMatchGroup(int group) {
269 accountName.setMatchGroup(group);
271 public String getAccountName() {
272 return accountName.getValue();
274 public void setAccountName(String accountName) {
275 this.accountName.setValue(accountName);
277 public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
278 public boolean hasLiteralAmount() {
279 return amount.hasLiteralValue();
281 public int getAmountMatchGroup() {
282 return amount.getMatchGroup();
284 public void setAmountMatchGroup(int group) {
285 amount.setMatchGroup(group);
287 public float getAmount() {
288 return amount.getValue();
290 public void setAmount(float amount) {
291 this.amount.setValue(amount);
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);
302 public boolean hasLiteralAccountComment() {
303 return accountComment.hasLiteralValue();
305 public boolean equalContents(AccountRow o) {
306 return amount.equals(o.amount) && accountName.equals(o.accountName) &&
307 accountComment.equals(o.accountComment);
309 public void switchToLiteralAmount() {
310 amount.switchToLiteral();
312 public void switchToLiteralAccountName() {
313 accountName.switchToLiteral();
315 public void switchToLiteralAccountComment() {
316 accountComment.switchToLiteral();
318 public PatternAccount toDBO(@NonNull Long patternId) {
319 PatternAccount result = new PatternAccount((id <= 0L) ? null : id, patternId, position);
321 if (accountName.hasLiteralValue())
322 result.setAccountName(accountName.getValue());
324 result.setAccountNameMatchGroup(accountName.getMatchGroup());
326 if (accountComment.hasLiteralValue())
327 result.setAccountComment(accountComment.getValue());
329 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
331 if (amount.hasLiteralValue())
332 result.setAmount(amount.getValue());
334 result.setAmountMatchGroup(amount.getMatchGroup());
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);
357 super(Type.HEADER, -1, -1);
359 public Header(Header origin) {
362 testText = origin.testText;
363 setPattern(origin.pattern);
365 transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
366 transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
368 dateYear = new PossiblyMatchedValue<>(origin.dateYear);
369 dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
370 dateDay = new PossiblyMatchedValue<>(origin.dateDay);
372 public String getName() {
375 public void setName(String name) {
378 public String getPattern() {
381 public void setPattern(String pattern) {
382 this.pattern = pattern;
383 if (pattern != null) {
385 this.compiledPattern = Pattern.compile(pattern);
386 this.patternError = null;
388 catch (PatternSyntaxException e) {
389 this.compiledPattern = null;
390 this.patternError = e.getMessage();
394 patternError = "Missing pattern";
399 public String toString() {
400 return super.toString() +
401 String.format(" name[%s] pat[%s] test[%s]", name, pattern, testText);
403 public String getTestText() {
406 public void setTestText(String testText) {
407 this.testText = testText;
409 public String getTransactionDescription() {
410 return transactionDescription.getValue();
412 public void setTransactionDescription(String transactionDescription) {
413 this.transactionDescription.setValue(transactionDescription);
415 public String getTransactionComment() {
416 return transactionComment.getValue();
418 public void setTransactionComment(String transactionComment) {
419 this.transactionComment.setValue(transactionComment);
421 public short getDateYear() {
422 return dateYear.getValue();
424 public void setDateYear(short dateYear) {
425 this.dateYear.setValue(dateYear);
427 public short getDateMonth() {
428 return dateMonth.getValue();
430 public void setDateMonth(short dateMonth) {
431 this.dateMonth.setValue(dateMonth);
433 public short getDateDay() {
434 return dateDay.getValue();
436 public void setDateDay(short dateDay) {
437 this.dateDay.setValue(dateDay);
439 public int getDateYearMatchGroup() {
440 return dateYear.getMatchGroup();
442 public void setDateYearMatchGroup(int dateYearMatchGroup) {
443 this.dateYear.setMatchGroup(dateYearMatchGroup);
445 public int getDateMonthMatchGroup() {
446 return dateMonth.getMatchGroup();
448 public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
449 this.dateMonth.setMatchGroup(dateMonthMatchGroup);
451 public int getDateDayMatchGroup() {
452 return dateDay.getMatchGroup();
454 public void setDateDayMatchGroup(int dateDayMatchGroup) {
455 this.dateDay.setMatchGroup(dateDayMatchGroup);
457 public boolean hasLiteralDateYear() {
458 return dateYear.hasLiteralValue();
460 public boolean hasLiteralDateMonth() {
461 return dateMonth.hasLiteralValue();
463 public boolean hasLiteralDateDay() {
464 return dateDay.hasLiteralValue();
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);
474 if (!dateYear.hasLiteralValue() && compiledPattern != null &&
475 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
476 return r.getString(R.string.invalid_matching_group_number);
478 if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
479 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
480 return r.getString(R.string.invalid_matching_group_number);
482 if (!dateDay.hasLiteralValue() && compiledPattern != null &&
483 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
484 return r.getString(R.string.invalid_matching_group_number);
489 public boolean equalContents(Header o) {
490 if (!dateDay.equals(o.dateDay))
492 if (!dateMonth.equals(o.dateMonth))
494 if (!dateYear.equals(o.dateYear))
496 if (!transactionDescription.equals(o.transactionDescription))
498 if (!transactionComment.equals(o.transactionComment))
501 return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
502 Misc.equalStrings(testText, o.testText);
504 public String getMatchGroupText(int group) {
505 if (compiledPattern != null && testText != null) {
506 Matcher m = compiledPattern.matcher(testText);
508 return m.group(group);
513 public Pattern getCompiledPattern() {
514 return compiledPattern;
516 public void switchToLiteralTransactionDescription() {
517 transactionDescription.switchToLiteral();
519 public void switchToLiteralTransactionComment() {
520 transactionComment.switchToLiteral();
522 public int getTransactionDescriptionMatchGroup() {
523 return transactionDescription.getMatchGroup();
525 public void setTransactionDescriptionMatchGroup(short group) {
526 transactionDescription.setMatchGroup(group);
528 public int getTransactionCommentMatchGroup() {
529 return transactionComment.getMatchGroup();
531 public void setTransactionCommentMatchGroup(short group) {
532 transactionComment.setMatchGroup(group);
534 public void switchToLiteralDateYear() {
535 dateYear.switchToLiteral();
537 public void switchToLiteralDateMonth() {
538 dateMonth.switchToLiteral();
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());
547 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
549 if (transactionComment.hasLiteralValue())
550 result.setTransactionComment(transactionComment.getValue());
552 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());