]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/model/TemplateDetailsItem.java
more pronounced day/month delimiters in the transaction list
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / model / TemplateDetailsItem.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 import android.graphics.Color;
22 import android.graphics.Typeface;
23 import android.text.SpannableString;
24 import android.text.Spanned;
25 import android.text.style.ForegroundColorSpan;
26 import android.text.style.StyleSpan;
27 import android.text.style.UnderlineSpan;
28
29 import androidx.annotation.NonNull;
30
31 import net.ktnx.mobileledger.R;
32 import net.ktnx.mobileledger.db.TemplateAccount;
33 import net.ktnx.mobileledger.db.TemplateBase;
34 import net.ktnx.mobileledger.db.TemplateHeader;
35 import net.ktnx.mobileledger.utils.Logger;
36 import net.ktnx.mobileledger.utils.Misc;
37
38 import org.jetbrains.annotations.Contract;
39 import org.jetbrains.annotations.NotNull;
40
41 import java.util.Locale;
42 import java.util.Objects;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45 import java.util.regex.PatternSyntaxException;
46
47 abstract public class TemplateDetailsItem {
48     private final Type type;
49     protected Long id;
50     protected long position;
51
52     protected TemplateDetailsItem(Type type) {
53         this.type = type;
54     }
55     @Contract(" -> new")
56     public static @NotNull TemplateDetailsItem.Header createHeader() {
57         return new Header();
58     }
59     public static @NotNull TemplateDetailsItem.Header createHeader(Header origin) {
60         return new Header(origin);
61     }
62     @Contract("-> new")
63     public static @NotNull TemplateDetailsItem.AccountRow createAccountRow() {
64         return new AccountRow();
65     }
66     public static TemplateDetailsItem fromRoomObject(TemplateBase p) {
67         if (p instanceof TemplateHeader) {
68             TemplateHeader ph = (TemplateHeader) p;
69             Header header = createHeader();
70             header.setId(ph.getId());
71             header.setName(ph.getName());
72             header.setPattern(ph.getRegularExpression());
73             header.setTestText(ph.getTestText());
74
75             if (ph.getTransactionDescriptionMatchGroup() == null)
76                 header.setTransactionDescription(ph.getTransactionDescription());
77             else
78                 header.setTransactionDescriptionMatchGroup(
79                         ph.getTransactionDescriptionMatchGroup());
80
81             if (ph.getTransactionCommentMatchGroup() == null)
82                 header.setTransactionComment(ph.getTransactionComment());
83             else
84                 header.setTransactionCommentMatchGroup(ph.getTransactionCommentMatchGroup());
85
86             if (ph.getDateDayMatchGroup() == null)
87                 header.setDateDay(ph.getDateDay());
88             else
89                 header.setDateDayMatchGroup(ph.getDateDayMatchGroup());
90
91             if (ph.getDateMonthMatchGroup() == null)
92                 header.setDateMonth(ph.getDateMonth());
93             else
94                 header.setDateMonthMatchGroup(ph.getDateMonthMatchGroup());
95
96             if (ph.getDateYearMatchGroup() == null)
97                 header.setDateYear(ph.getDateYear());
98             else
99                 header.setDateYearMatchGroup(ph.getDateYearMatchGroup());
100
101             header.setFallback(ph.isFallback());
102
103             return header;
104         }
105         else if (p instanceof TemplateAccount) {
106             TemplateAccount pa = (TemplateAccount) p;
107             AccountRow acc = createAccountRow();
108             acc.setId(pa.getId());
109             acc.setPosition(pa.getPosition());
110
111             if (pa.getAccountNameMatchGroup() == null)
112                 acc.setAccountName(Misc.nullIsEmpty(pa.getAccountName()));
113             else
114                 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
115
116             if (pa.getAccountCommentMatchGroup() == null)
117                 acc.setAccountComment(Misc.nullIsEmpty(pa.getAccountComment()));
118             else
119                 acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
120
121             if (pa.getCurrencyMatchGroup() == null) {
122                 acc.setCurrency(pa.getCurrencyObject());
123             }
124             else
125                 acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
126
127             final Integer amountMatchGroup = pa.getAmountMatchGroup();
128             if (amountMatchGroup != null && amountMatchGroup > 0) {
129                 acc.setAmountMatchGroup(amountMatchGroup);
130                 final Boolean negateAmount = pa.getNegateAmount();
131                 acc.setNegateAmount(negateAmount != null && negateAmount);
132             }
133             else
134                 acc.setAmount(pa.getAmount());
135
136             return acc;
137         }
138         else {
139             throw new IllegalStateException("Unexpected item class " + p.getClass());
140         }
141     }
142     public Header asHeaderItem() {
143         ensureType(Type.HEADER);
144         return (Header) this;
145     }
146     public AccountRow asAccountRowItem() {
147         ensureType(Type.ACCOUNT_ITEM);
148         return (AccountRow) this;
149     }
150     private void ensureType(Type type) {
151         if (this.type != type)
152             throw new IllegalStateException(
153                     String.format("Type is %s, but %s is required", this.type.toString(),
154                             type.toString()));
155     }
156     void ensureTrue(boolean flag) {
157         if (!flag)
158             throw new IllegalStateException(
159                     "Literal value requested, but it is matched via a pattern group");
160     }
161     void ensureFalse(boolean flag) {
162         if (flag)
163             throw new IllegalStateException("Matching group requested, but the value is a literal");
164     }
165     public long getId() {
166         return id;
167     }
168     public void setId(Long id) {
169         this.id = id;
170     }
171     public void setId(int id) {
172         this.id = (long) id;
173     }
174     public long getPosition() {
175         return position;
176     }
177     public void setPosition(long position) {
178         this.position = position;
179     }
180     abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
181     public Type getType() {
182         return type;
183     }
184     public enum Type {
185         HEADER(TYPE.header), ACCOUNT_ITEM(TYPE.accountItem);
186         final int index;
187         Type(int i) {
188             index = i;
189         }
190         public int toInt() {
191             return index;
192         }
193     }
194
195     static class PossiblyMatchedValue<T> {
196         private boolean literalValue;
197         private T value;
198         private int matchGroup;
199         public PossiblyMatchedValue() {
200             literalValue = true;
201             value = null;
202         }
203         public PossiblyMatchedValue(@NonNull PossiblyMatchedValue<T> origin) {
204             literalValue = origin.literalValue;
205             value = origin.value;
206             matchGroup = origin.matchGroup;
207         }
208         @NonNull
209         public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
210             PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
211             result.setValue(initialValue);
212             return result;
213         }
214         @NonNull
215         public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
216             PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
217             result.setValue(initialValue);
218             return result;
219         }
220         public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
221             PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
222             result.setValue(initialValue);
223             return result;
224         }
225         @NonNull
226         public static PossiblyMatchedValue<String> withLiteralString(String initialValue) {
227             PossiblyMatchedValue<String> result = new PossiblyMatchedValue<>();
228             result.setValue(initialValue);
229             return result;
230         }
231         public void copyFrom(@NonNull PossiblyMatchedValue<T> origin) {
232             literalValue = origin.literalValue;
233             value = origin.value;
234             matchGroup = origin.matchGroup;
235         }
236         public T getValue() {
237             if (!literalValue)
238                 throw new IllegalStateException("Value is not literal");
239             return value;
240         }
241         public void setValue(T newValue) {
242             value = newValue;
243             literalValue = true;
244         }
245         public boolean hasLiteralValue() {
246             return literalValue;
247         }
248         public int getMatchGroup() {
249             if (literalValue)
250                 throw new IllegalStateException("Value is literal");
251             return matchGroup;
252         }
253         public void setMatchGroup(int group) {
254             this.matchGroup = group;
255             literalValue = false;
256         }
257         public boolean equals(PossiblyMatchedValue<T> other) {
258             if (!other.literalValue == literalValue)
259                 return false;
260             if (literalValue) {
261                 if (value == null)
262                     return other.value == null;
263                 return value.equals(other.value);
264             }
265             else
266                 return matchGroup == other.matchGroup;
267         }
268         public void switchToLiteral() {
269             literalValue = true;
270         }
271         public String toString() {
272             if (literalValue)
273                 if (value == null)
274                     return "<null>";
275                 else
276                     return value.toString();
277             if (matchGroup > 0)
278                 return "grp:" + matchGroup;
279             return "<null>";
280         }
281         public boolean isEmpty() {
282             if (literalValue)
283                 return value == null || Misc.emptyIsNull(value.toString()) == null;
284
285             return matchGroup > 0;
286         }
287     }
288
289     public static class TYPE {
290         public static final int header = 0;
291         public static final int accountItem = 1;
292     }
293
294     public static class AccountRow extends TemplateDetailsItem {
295         private final PossiblyMatchedValue<String> accountName =
296                 PossiblyMatchedValue.withLiteralString("");
297         private final PossiblyMatchedValue<String> accountComment =
298                 PossiblyMatchedValue.withLiteralString("");
299         private final PossiblyMatchedValue<Float> amount =
300                 PossiblyMatchedValue.withLiteralFloat(null);
301         private final PossiblyMatchedValue<net.ktnx.mobileledger.db.Currency> currency =
302                 new PossiblyMatchedValue<>();
303         private boolean negateAmount;
304         public AccountRow() {
305             super(Type.ACCOUNT_ITEM);
306         }
307         public AccountRow(AccountRow origin) {
308             super(Type.ACCOUNT_ITEM);
309             id = origin.id;
310             position = origin.position;
311             accountName.copyFrom(origin.accountName);
312             accountComment.copyFrom(origin.accountComment);
313             amount.copyFrom(origin.amount);
314             currency.copyFrom(origin.currency);
315             negateAmount = origin.negateAmount;
316         }
317         public boolean isNegateAmount() {
318             return negateAmount;
319         }
320         public void setNegateAmount(boolean negateAmount) {
321             this.negateAmount = negateAmount;
322         }
323         public int getAccountCommentMatchGroup() {
324             return accountComment.getMatchGroup();
325         }
326         public void setAccountCommentMatchGroup(int group) {
327             accountComment.setMatchGroup(group);
328         }
329         public String getAccountComment() {
330             return accountComment.getValue();
331         }
332         public void setAccountComment(String comment) {
333             this.accountComment.setValue(comment);
334         }
335         public int getCurrencyMatchGroup() {
336             return currency.getMatchGroup();
337         }
338         public void setCurrencyMatchGroup(int group) {
339             currency.setMatchGroup(group);
340         }
341         public net.ktnx.mobileledger.db.Currency getCurrency() {
342             return currency.getValue();
343         }
344         public void setCurrency(net.ktnx.mobileledger.db.Currency currency) {
345             this.currency.setValue(currency);
346         }
347         public int getAccountNameMatchGroup() {
348             return accountName.getMatchGroup();
349         }
350         public void setAccountNameMatchGroup(int group) {
351             accountName.setMatchGroup(group);
352         }
353         public String getAccountName() {
354             return accountName.getValue();
355         }
356         public void setAccountName(String accountName) {
357             this.accountName.setValue(accountName);
358         }
359         public boolean hasLiteralAccountName() { return accountName.hasLiteralValue(); }
360         public boolean hasLiteralAmount() {
361             return amount.hasLiteralValue();
362         }
363         public int getAmountMatchGroup() {
364             return amount.getMatchGroup();
365         }
366         public void setAmountMatchGroup(int group) {
367             amount.setMatchGroup(group);
368         }
369         public Float getAmount() {
370             return amount.getValue();
371         }
372         public void setAmount(Float amount) {
373             this.amount.setValue(amount);
374         }
375         public String getProblem(@NonNull Resources r, int patternGroupCount) {
376             if (Misc.emptyIsNull(accountName.getValue()) == null)
377                 return r.getString(R.string.account_name_is_empty);
378             if (!amount.hasLiteralValue() &&
379                 (amount.getMatchGroup() < 1 || amount.getMatchGroup() > patternGroupCount))
380                 return r.getString(R.string.invalid_matching_group_number);
381
382             return null;
383         }
384         public boolean hasLiteralAccountComment() {
385             return accountComment.hasLiteralValue();
386         }
387         public boolean hasLiteralCurrency() { return currency.hasLiteralValue(); }
388         public boolean equalContents(AccountRow o) {
389             if (position != o.position) {
390                 Logger.debug("cmpAcc",
391                         String.format(Locale.US, "[%d] != [%d]: pos %d != pos %d", getId(),
392                                 o.getId(), position, o.position));
393                 return false;
394             }
395             return amount.equals(o.amount) && accountName.equals(o.accountName) &&
396                    position == o.position && accountComment.equals(o.accountComment) &&
397                    negateAmount == o.negateAmount;
398         }
399         public void switchToLiteralAmount() {
400             amount.switchToLiteral();
401         }
402         public void switchToLiteralCurrency() {
403             currency.switchToLiteral();
404         }
405         public void switchToLiteralAccountName() {
406             accountName.switchToLiteral();
407         }
408         public void switchToLiteralAccountComment() {
409             accountComment.switchToLiteral();
410         }
411         public TemplateAccount toDBO(@NonNull Long patternId) {
412             TemplateAccount result = new TemplateAccount(id, patternId, position);
413
414             if (accountName.hasLiteralValue())
415                 result.setAccountName(accountName.getValue());
416             else
417                 result.setAccountNameMatchGroup(accountName.getMatchGroup());
418
419             if (accountComment.hasLiteralValue())
420                 result.setAccountComment(accountComment.getValue());
421             else
422                 result.setAccountCommentMatchGroup(accountComment.getMatchGroup());
423
424             if (amount.hasLiteralValue()) {
425                 result.setAmount(amount.getValue());
426                 result.setNegateAmount(null);
427             }
428             else {
429                 result.setAmountMatchGroup(amount.getMatchGroup());
430                 result.setNegateAmount(negateAmount ? true : null);
431             }
432
433             if (currency.hasLiteralValue()) {
434                 net.ktnx.mobileledger.db.Currency c = currency.getValue();
435                 result.setCurrency((c == null) ? null : c.getId());
436             }
437             else {
438                 result.setCurrencyMatchGroup(currency.getMatchGroup());
439             }
440
441             return result;
442         }
443         public boolean isEmpty() {
444             return accountName.isEmpty() && accountComment.isEmpty() && amount.isEmpty();
445         }
446     }
447
448     public static class Header extends TemplateDetailsItem {
449         private String pattern = "";
450         private String testText = "";
451         private String name = "";
452         private Pattern compiledPattern;
453         private String patternError;
454         private PossiblyMatchedValue<String> transactionDescription =
455                 PossiblyMatchedValue.withLiteralString("");
456         private PossiblyMatchedValue<String> transactionComment =
457                 PossiblyMatchedValue.withLiteralString("");
458         private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
459         private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
460         private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
461         private SpannableString testMatch;
462         private boolean isFallback;
463         private Header() {
464             super(Type.HEADER);
465         }
466         public Header(Header origin) {
467             this();
468             id = origin.id;
469             name = origin.name;
470             testText = origin.testText;
471             testMatch = origin.testMatch;
472             setPattern(origin.pattern);
473
474             transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
475             transactionComment = new PossiblyMatchedValue<>(origin.transactionComment);
476
477             dateYear = new PossiblyMatchedValue<>(origin.dateYear);
478             dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
479             dateDay = new PossiblyMatchedValue<>(origin.dateDay);
480
481             isFallback = origin.isFallback;
482         }
483         private static StyleSpan capturedSpan() { return new StyleSpan(Typeface.BOLD); }
484         private static UnderlineSpan matchedSpan() { return new UnderlineSpan(); }
485         private static ForegroundColorSpan notMatchedSpan() {
486             return new ForegroundColorSpan(Color.GRAY);
487         }
488         public boolean isFallback() {
489             return isFallback;
490         }
491         public void setFallback(boolean fallback) {
492             this.isFallback = fallback;
493         }
494         public String getName() {
495             return name;
496         }
497         public void setName(String name) {
498             this.name = name;
499         }
500         public String getPattern() {
501             return pattern;
502         }
503         public void setPattern(String pattern) {
504             this.pattern = pattern;
505             try {
506                 this.compiledPattern = Pattern.compile(pattern);
507                 checkPatternMatch();
508             }
509             catch (PatternSyntaxException ex) {
510                 patternError = ex.getDescription();
511                 compiledPattern = null;
512
513                 testMatch = new SpannableString(testText);
514                 if (!testText.isEmpty())
515                     testMatch.setSpan(notMatchedSpan(), 0, testText.length() - 1,
516                             Spanned.SPAN_INCLUSIVE_INCLUSIVE);
517             }
518         }
519         @NonNull
520         @Override
521         public String toString() {
522             return super.toString() +
523                    String.format(" name[%s] pat[%s] test[%s] tran[%s] com[%s]", name, pattern,
524                            testText, transactionDescription, transactionComment);
525         }
526         public String getTestText() {
527             return testText;
528         }
529         public void setTestText(String testText) {
530             this.testText = testText;
531
532             checkPatternMatch();
533         }
534         public String getTransactionDescription() {
535             return transactionDescription.getValue();
536         }
537         public void setTransactionDescription(String transactionDescription) {
538             this.transactionDescription.setValue(transactionDescription);
539         }
540         public String getTransactionComment() {
541             return transactionComment.getValue();
542         }
543         public void setTransactionComment(String transactionComment) {
544             this.transactionComment.setValue(transactionComment);
545         }
546         public Integer getDateYear() {
547             return dateYear.getValue();
548         }
549         public void setDateYear(Integer dateYear) {
550             this.dateYear.setValue(dateYear);
551         }
552         public Integer getDateMonth() {
553             return dateMonth.getValue();
554         }
555         public void setDateMonth(Integer dateMonth) {
556             this.dateMonth.setValue(dateMonth);
557         }
558         public Integer getDateDay() {
559             return dateDay.getValue();
560         }
561         public void setDateDay(Integer dateDay) {
562             this.dateDay.setValue(dateDay);
563         }
564         public int getDateYearMatchGroup() {
565             return dateYear.getMatchGroup();
566         }
567         public void setDateYearMatchGroup(int dateYearMatchGroup) {
568             this.dateYear.setMatchGroup(dateYearMatchGroup);
569         }
570         public int getDateMonthMatchGroup() {
571             return dateMonth.getMatchGroup();
572         }
573         public void setDateMonthMatchGroup(int dateMonthMatchGroup) {
574             this.dateMonth.setMatchGroup(dateMonthMatchGroup);
575         }
576         public int getDateDayMatchGroup() {
577             return dateDay.getMatchGroup();
578         }
579         public void setDateDayMatchGroup(int dateDayMatchGroup) {
580             this.dateDay.setMatchGroup(dateDayMatchGroup);
581         }
582         public boolean hasLiteralDateYear() {
583             return dateYear.hasLiteralValue();
584         }
585         public boolean hasLiteralDateMonth() {
586             return dateMonth.hasLiteralValue();
587         }
588         public boolean hasLiteralDateDay() {
589             return dateDay.hasLiteralValue();
590         }
591         public boolean hasLiteralTransactionDescription() { return transactionDescription.hasLiteralValue(); }
592         public boolean hasLiteralTransactionComment() { return transactionComment.hasLiteralValue(); }
593         public String getProblem(@NonNull Resources r, int patternGroupCount) {
594             if (patternError != null)
595                 return r.getString(R.string.pattern_has_errors) + ": " + patternError;
596             if (Misc.emptyIsNull(pattern) == null)
597                 return r.getString(R.string.pattern_is_empty);
598
599             if (!dateYear.hasLiteralValue() && compiledPattern != null &&
600                 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
601                 return r.getString(R.string.invalid_matching_group_number);
602
603             if (!dateMonth.hasLiteralValue() && compiledPattern != null &&
604                 (dateMonth.getMatchGroup() < 1 || dateMonth.getMatchGroup() > patternGroupCount))
605                 return r.getString(R.string.invalid_matching_group_number);
606
607             if (!dateDay.hasLiteralValue() && compiledPattern != null &&
608                 (dateDay.getMatchGroup() < 1 || dateDay.getMatchGroup() > patternGroupCount))
609                 return r.getString(R.string.invalid_matching_group_number);
610
611             return null;
612         }
613
614         public boolean equalContents(Header o) {
615             if (!dateDay.equals(o.dateDay))
616                 return false;
617             if (!dateMonth.equals(o.dateMonth))
618                 return false;
619             if (!dateYear.equals(o.dateYear))
620                 return false;
621             if (!transactionDescription.equals(o.transactionDescription))
622                 return false;
623             if (!transactionComment.equals(o.transactionComment))
624                 return true;
625
626             return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
627                    Misc.equalStrings(testText, o.testText) &&
628                    Misc.equalStrings(patternError, o.patternError) &&
629                    Objects.equals(testMatch, o.testMatch) && isFallback == o.isFallback;
630         }
631         public String getMatchGroupText(int group) {
632             if (compiledPattern != null && testText != null) {
633                 Matcher m = compiledPattern.matcher(testText);
634                 if (m.matches())
635                     return m.group(group);
636             }
637
638             return "ø";
639         }
640         public Pattern getCompiledPattern() {
641             return compiledPattern;
642         }
643         public void switchToLiteralTransactionDescription() {
644             transactionDescription.switchToLiteral();
645         }
646         public void switchToLiteralTransactionComment() {
647             transactionComment.switchToLiteral();
648         }
649         public int getTransactionDescriptionMatchGroup() {
650             return transactionDescription.getMatchGroup();
651         }
652         public void setTransactionDescriptionMatchGroup(int group) {
653             transactionDescription.setMatchGroup(group);
654         }
655         public int getTransactionCommentMatchGroup() {
656             return transactionComment.getMatchGroup();
657         }
658         public void setTransactionCommentMatchGroup(int group) {
659             transactionComment.setMatchGroup(group);
660         }
661         public void switchToLiteralDateYear() {
662             dateYear.switchToLiteral();
663         }
664         public void switchToLiteralDateMonth() {
665             dateMonth.switchToLiteral();
666         }
667         public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
668         public TemplateHeader toDBO() {
669             TemplateHeader result = new TemplateHeader(id, name, pattern);
670
671             if (Misc.emptyIsNull(testText) != null)
672                 result.setTestText(testText);
673
674             if (transactionDescription.hasLiteralValue())
675                 result.setTransactionDescription(transactionDescription.getValue());
676             else
677                 result.setTransactionDescriptionMatchGroup(transactionDescription.getMatchGroup());
678
679             if (transactionComment.hasLiteralValue())
680                 result.setTransactionComment(transactionComment.getValue());
681             else
682                 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
683
684             if (dateYear.hasLiteralValue())
685                 result.setDateYear(dateYear.getValue());
686             else
687                 result.setDateYearMatchGroup(dateYear.getMatchGroup());
688
689             if (dateMonth.hasLiteralValue())
690                 result.setDateMonth(dateMonth.getValue());
691             else
692                 result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
693
694             if (dateDay.hasLiteralValue())
695                 result.setDateDay(dateDay.getValue());
696             else
697                 result.setDateDayMatchGroup(dateDay.getMatchGroup());
698
699             result.setFallback(isFallback);
700
701             return result;
702         }
703         public SpannableString getTestMatch() {
704             return testMatch;
705         }
706         public void checkPatternMatch() {
707             patternError = null;
708             testMatch = null;
709
710             if (pattern != null) {
711                 try {
712                     if (Misc.emptyIsNull(testText) != null) {
713                         SpannableString ss = new SpannableString(testText);
714                         Matcher m = compiledPattern.matcher(testText);
715                         if (m.find()) {
716                             if (m.start() > 0)
717                                 ss.setSpan(notMatchedSpan(), 0, m.start(),
718                                         Spanned.SPAN_INCLUSIVE_INCLUSIVE);
719                             if (m.end() < testText.length() - 1)
720                                 ss.setSpan(notMatchedSpan(), m.end(), testText.length(),
721                                         Spanned.SPAN_INCLUSIVE_INCLUSIVE);
722
723                             ss.setSpan(matchedSpan(), m.start(0), m.end(0),
724                                     Spanned.SPAN_INCLUSIVE_INCLUSIVE);
725
726                             if (m.groupCount() > 0) {
727                                 for (int g = 1; g <= m.groupCount(); g++) {
728                                     ss.setSpan(capturedSpan(), m.start(g), m.end(g),
729                                             Spanned.SPAN_INCLUSIVE_INCLUSIVE);
730                                 }
731                             }
732                         }
733                         else {
734                             patternError = "Pattern does not match";
735                             ss.setSpan(new ForegroundColorSpan(Color.GRAY), 0,
736                                     testText.length() - 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
737                         }
738
739                         testMatch = ss;
740                     }
741                 }
742                 catch (PatternSyntaxException e) {
743                     this.compiledPattern = null;
744                     this.patternError = e.getMessage();
745                 }
746             }
747             else {
748                 patternError = "Missing pattern";
749             }
750         }
751         public String getPatternError() {
752             return patternError;
753         }
754         public SpannableString testMatch() {
755             return testMatch;
756         }
757     }
758 }