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