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