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