JAVA: Move java code to java/libphonenumber.
[platform/upstream/libphonenumber.git] / java / libphonenumber / src / com / google / i18n / phonenumbers / AsYouTypeFormatter.java
1 /*
2  * Copyright (C) 2009 The Libphonenumber Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.i18n.phonenumbers;
18
19 import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat;
20 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
21
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 /**
29  * A formatter which formats phone numbers as they are entered.
30  *
31  * <p>An AsYouTypeFormatter can be created by invoking
32  * {@link PhoneNumberUtil#getAsYouTypeFormatter}. After that, digits can be added by invoking
33  * {@link #inputDigit} on the formatter instance, and the partially formatted phone number will be
34  * returned each time a digit is added. {@link #clear} can be invoked before formatting a new
35  * number.
36  *
37  * <p>See the unittests for more details on how the formatter is to be used.
38  *
39  * @author Shaopeng Jia
40  */
41 public class AsYouTypeFormatter {
42   private String currentOutput = "";
43   private StringBuilder formattingTemplate = new StringBuilder();
44   // The pattern from numberFormat that is currently used to create formattingTemplate.
45   private String currentFormattingPattern = "";
46   private StringBuilder accruedInput = new StringBuilder();
47   private StringBuilder accruedInputWithoutFormatting = new StringBuilder();
48   // This indicates whether AsYouTypeFormatter is currently doing the formatting.
49   private boolean ableToFormat = true;
50   // Set to true when users enter their own formatting. AsYouTypeFormatter will do no formatting at
51   // all when this is set to true.
52   private boolean inputHasFormatting = false;
53   private boolean isInternationalFormatting = false;
54   private boolean isExpectingCountryCallingCode = false;
55   private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
56   private String defaultCountry;
57
58   private static final PhoneMetadata EMPTY_METADATA =
59       new PhoneMetadata().setInternationalPrefix("NA");
60   private PhoneMetadata defaultMetaData;
61   private PhoneMetadata currentMetaData;
62
63   // A pattern that is used to match character classes in regular expressions. An example of a
64   // character class is [1-4].
65   private static final Pattern CHARACTER_CLASS_PATTERN = Pattern.compile("\\[([^\\[\\]])*\\]");
66   // Any digit in a regular expression that actually denotes a digit. For example, in the regular
67   // expression 80[0-2]\d{6,10}, the first 2 digits (8 and 0) are standalone digits, but the rest
68   // are not.
69   // Two look-aheads are needed because the number following \\d could be a two-digit number, since
70   // the phone number can be as long as 15 digits.
71   private static final Pattern STANDALONE_DIGIT_PATTERN = Pattern.compile("\\d(?=[^,}][^,}])");
72
73   // A pattern that is used to determine if a numberFormat under availableFormats is eligible to be
74   // used by the AYTF. It is eligible when the format element under numberFormat contains groups of
75   // the dollar sign followed by a single digit, separated by valid phone number punctuation. This
76   // prevents invalid punctuation (such as the star sign in Israeli star numbers) getting into the
77   // output of the AYTF.
78   private static final Pattern ELIGIBLE_FORMAT_PATTERN =
79       Pattern.compile("[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*" +
80           "(\\$\\d" + "[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*)+");
81
82   // This is the minimum length of national number accrued that is required to trigger the
83   // formatter. The first element of the leadingDigitsPattern of each numberFormat contains a
84   // regular expression that matches up to this number of digits.
85   private static final int MIN_LEADING_DIGITS_LENGTH = 3;
86
87   // The digits that have not been entered yet will be represented by a \u2008, the punctuation
88   // space.
89   private String digitPlaceholder = "\u2008";
90   private Pattern digitPattern = Pattern.compile(digitPlaceholder);
91   private int lastMatchPosition = 0;
92   // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as
93   // found in the original sequence of characters the user entered.
94   private int originalPosition = 0;
95   // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as
96   // found in accruedInputWithoutFormatting.
97   private int positionToRemember = 0;
98   // This contains anything that has been entered so far preceding the national significant number,
99   // and it is formatted (e.g. with space inserted). For example, this can contain IDD, country
100   // code, and/or NDD, etc.
101   private StringBuilder prefixBeforeNationalNumber = new StringBuilder();
102   // This contains the national prefix that has been extracted. It contains only digits without
103   // formatting.
104   private String nationalPrefixExtracted = "";
105   private StringBuilder nationalNumber = new StringBuilder();
106   private List<NumberFormat> possibleFormats = new ArrayList<NumberFormat>();
107
108     // A cache for frequently used country-specific regular expressions.
109   private RegexCache regexCache = new RegexCache(64);
110
111   /**
112    * Constructs an as-you-type formatter. Should be obtained from {@link
113    * PhoneNumberUtil#getAsYouTypeFormatter}.
114    *
115    * @param regionCode  the country/region where the phone number is being entered
116    */
117   AsYouTypeFormatter(String regionCode) {
118     defaultCountry = regionCode;
119     currentMetaData = getMetadataForRegion(defaultCountry);
120     defaultMetaData = currentMetaData;
121   }
122
123   // The metadata needed by this class is the same for all regions sharing the same country calling
124   // code. Therefore, we return the metadata for "main" region for this country calling code.
125   private PhoneMetadata getMetadataForRegion(String regionCode) {
126     int countryCallingCode = phoneUtil.getCountryCodeForRegion(regionCode);
127     String mainCountry = phoneUtil.getRegionCodeForCountryCode(countryCallingCode);
128     PhoneMetadata metadata = phoneUtil.getMetadataForRegion(mainCountry);
129     if (metadata != null) {
130       return metadata;
131     }
132     // Set to a default instance of the metadata. This allows us to function with an incorrect
133     // region code, even if formatting only works for numbers specified with "+".
134     return EMPTY_METADATA;
135   }
136
137   // Returns true if a new template is created as opposed to reusing the existing template.
138   private boolean maybeCreateNewTemplate() {
139     // When there are multiple available formats, the formatter uses the first format where a
140     // formatting template could be created.
141     Iterator<NumberFormat> it = possibleFormats.iterator();
142     while (it.hasNext()) {
143       NumberFormat numberFormat = it.next();
144       String pattern = numberFormat.getPattern();
145       if (currentFormattingPattern.equals(pattern)) {
146         return false;
147       }
148       if (createFormattingTemplate(numberFormat)) {
149         currentFormattingPattern = pattern;
150         // With a new formatting template, the matched position using the old template needs to be
151         // reset.
152         lastMatchPosition = 0;
153         return true;
154       } else {  // Remove the current number format from possibleFormats.
155         it.remove();
156       }
157     }
158     ableToFormat = false;
159     return false;
160   }
161
162   private void getAvailableFormats(String leadingThreeDigits) {
163     List<NumberFormat> formatList =
164         (isInternationalFormatting && currentMetaData.intlNumberFormatSize() > 0)
165         ? currentMetaData.intlNumberFormats()
166         : currentMetaData.numberFormats();
167     for (NumberFormat format : formatList) {
168       if (isFormatEligible(format.getFormat())) {
169         possibleFormats.add(format);
170       }
171     }
172     narrowDownPossibleFormats(leadingThreeDigits);
173   }
174
175   private boolean isFormatEligible(String format) {
176     return ELIGIBLE_FORMAT_PATTERN.matcher(format).matches();
177   }
178
179   private void narrowDownPossibleFormats(String leadingDigits) {
180     int indexOfLeadingDigitsPattern = leadingDigits.length() - MIN_LEADING_DIGITS_LENGTH;
181     Iterator<NumberFormat> it = possibleFormats.iterator();
182     while (it.hasNext()) {
183       NumberFormat format = it.next();
184       if (format.leadingDigitsPatternSize() > indexOfLeadingDigitsPattern) {
185         Pattern leadingDigitsPattern =
186             regexCache.getPatternForRegex(
187                 format.getLeadingDigitsPattern(indexOfLeadingDigitsPattern));
188         Matcher m = leadingDigitsPattern.matcher(leadingDigits);
189         if (!m.lookingAt()) {
190           it.remove();
191         }
192       } // else the particular format has no more specific leadingDigitsPattern, and it should be
193         // retained.
194     }
195   }
196
197   private boolean createFormattingTemplate(NumberFormat format) {
198     String numberPattern = format.getPattern();
199
200     // The formatter doesn't format numbers when numberPattern contains "|", e.g.
201     // (20|3)\d{4}. In those cases we quickly return.
202     if (numberPattern.indexOf('|') != -1) {
203       return false;
204     }
205
206     // Replace anything in the form of [..] with \d
207     numberPattern = CHARACTER_CLASS_PATTERN.matcher(numberPattern).replaceAll("\\\\d");
208
209     // Replace any standalone digit (not the one in d{}) with \d
210     numberPattern = STANDALONE_DIGIT_PATTERN.matcher(numberPattern).replaceAll("\\\\d");
211     formattingTemplate.setLength(0);
212     String tempTemplate = getFormattingTemplate(numberPattern, format.getFormat());
213     if (tempTemplate.length() > 0) {
214       formattingTemplate.append(tempTemplate);
215       return true;
216     }
217     return false;
218   }
219
220   // Gets a formatting template which can be used to efficiently format a partial number where
221   // digits are added one by one.
222   private String getFormattingTemplate(String numberPattern, String numberFormat) {
223     // Creates a phone number consisting only of the digit 9 that matches the
224     // numberPattern by applying the pattern to the longestPhoneNumber string.
225     String longestPhoneNumber = "999999999999999";
226     Matcher m = regexCache.getPatternForRegex(numberPattern).matcher(longestPhoneNumber);
227     m.find();  // this will always succeed
228     String aPhoneNumber = m.group();
229     // No formatting template can be created if the number of digits entered so far is longer than
230     // the maximum the current formatting rule can accommodate.
231     if (aPhoneNumber.length() < nationalNumber.length()) {
232       return "";
233     }
234     // Formats the number according to numberFormat
235     String template = aPhoneNumber.replaceAll(numberPattern, numberFormat);
236     // Replaces each digit with character digitPlaceholder
237     template = template.replaceAll("9", digitPlaceholder);
238     return template;
239   }
240
241   /**
242    * Clears the internal state of the formatter, so it can be reused.
243    */
244   public void clear() {
245     currentOutput = "";
246     accruedInput.setLength(0);
247     accruedInputWithoutFormatting.setLength(0);
248     formattingTemplate.setLength(0);
249     lastMatchPosition = 0;
250     currentFormattingPattern = "";
251     prefixBeforeNationalNumber.setLength(0);
252     nationalPrefixExtracted = "";
253     nationalNumber.setLength(0);
254     ableToFormat = true;
255     inputHasFormatting = false;
256     positionToRemember = 0;
257     originalPosition = 0;
258     isInternationalFormatting = false;
259     isExpectingCountryCallingCode = false;
260     possibleFormats.clear();
261     if (!currentMetaData.equals(defaultMetaData)) {
262       currentMetaData = getMetadataForRegion(defaultCountry);
263     }
264   }
265
266   /**
267    * Formats a phone number on-the-fly as each digit is entered.
268    *
269    * @param nextChar  the most recently entered digit of a phone number. Formatting characters are
270    *     allowed, but as soon as they are encountered this method formats the number as entered and
271    *     not "as you type" anymore. Full width digits and Arabic-indic digits are allowed, and will
272    *     be shown as they are.
273    * @return  the partially formatted phone number.
274    */
275   public String inputDigit(char nextChar) {
276     currentOutput = inputDigitWithOptionToRememberPosition(nextChar, false);
277     return currentOutput;
278   }
279
280   /**
281    * Same as {@link #inputDigit}, but remembers the position where {@code nextChar} is inserted, so
282    * that it can be retrieved later by using {@link #getRememberedPosition}. The remembered
283    * position will be automatically adjusted if additional formatting characters are later
284    * inserted/removed in front of {@code nextChar}.
285    */
286   public String inputDigitAndRememberPosition(char nextChar) {
287     currentOutput = inputDigitWithOptionToRememberPosition(nextChar, true);
288     return currentOutput;
289   }
290
291   @SuppressWarnings("fallthrough")
292   private String inputDigitWithOptionToRememberPosition(char nextChar, boolean rememberPosition) {
293     accruedInput.append(nextChar);
294     if (rememberPosition) {
295       originalPosition = accruedInput.length();
296     }
297     // We do formatting on-the-fly only when each character entered is either a digit, or a plus
298     // sign (accepted at the start of the number only).
299     if (!isDigitOrLeadingPlusSign(nextChar)) {
300       ableToFormat = false;
301       inputHasFormatting = true;
302     } else {
303       nextChar = normalizeAndAccrueDigitsAndPlusSign(nextChar, rememberPosition);
304     }
305     if (!ableToFormat) {
306       // When we are unable to format because of reasons other than that formatting chars have been
307       // entered, it can be due to really long IDDs or NDDs. If that is the case, we might be able
308       // to do formatting again after extracting them.
309       if (inputHasFormatting) {
310         return accruedInput.toString();
311       } else if (attemptToExtractIdd()) {
312         if (attemptToExtractCountryCallingCode()) {
313           return attemptToChoosePatternWithPrefixExtracted();
314         }
315       } else if (ableToExtractLongerNdd()) {
316         // Add an additional space to separate long NDD and national significant number for
317         // readability.
318         prefixBeforeNationalNumber.append(" ");
319         return attemptToChoosePatternWithPrefixExtracted();
320       }
321       return accruedInput.toString();
322     }
323
324     // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH digits (the plus
325     // sign is counted as a digit as well for this purpose) have been entered.
326     switch (accruedInputWithoutFormatting.length()) {
327       case 0:
328       case 1:
329       case 2:
330         return accruedInput.toString();
331       case 3:
332         if (attemptToExtractIdd()) {
333           isExpectingCountryCallingCode = true;
334         } else {  // No IDD or plus sign is found, might be entering in national format.
335           nationalPrefixExtracted = removeNationalPrefixFromNationalNumber();
336           return attemptToChooseFormattingPattern();
337         }
338       default:
339         if (isExpectingCountryCallingCode) {
340           if (attemptToExtractCountryCallingCode()) {
341             isExpectingCountryCallingCode = false;
342           }
343           return prefixBeforeNationalNumber + nationalNumber.toString();
344         }
345         if (possibleFormats.size() > 0) {  // The formatting pattern is already chosen.
346           String tempNationalNumber = inputDigitHelper(nextChar);
347           // See if the accrued digits can be formatted properly already. If not, use the results
348           // from inputDigitHelper, which does formatting based on the formatting pattern chosen.
349           String formattedNumber = attemptToFormatAccruedDigits();
350           if (formattedNumber.length() > 0) {
351             return formattedNumber;
352           }
353           narrowDownPossibleFormats(nationalNumber.toString());
354           if (maybeCreateNewTemplate()) {
355             return inputAccruedNationalNumber();
356           }
357           return ableToFormat
358              ? prefixBeforeNationalNumber + tempNationalNumber
359              : tempNationalNumber;
360         } else {
361           return attemptToChooseFormattingPattern();
362         }
363     }
364   }
365
366   private String attemptToChoosePatternWithPrefixExtracted() {
367     ableToFormat = true;
368     isExpectingCountryCallingCode = false;
369     possibleFormats.clear();
370     return attemptToChooseFormattingPattern();
371   }
372
373   // Some national prefixes are a substring of others. If extracting the shorter NDD doesn't result
374   // in a number we can format, we try to see if we can extract a longer version here.
375   private boolean ableToExtractLongerNdd() {
376     if (nationalPrefixExtracted.length() > 0) {
377       // Put the extracted NDD back to the national number before attempting to extract a new NDD.
378       nationalNumber.insert(0, nationalPrefixExtracted);
379       // Remove the previously extracted NDD from prefixBeforeNationalNumber. We cannot simply set
380       // it to empty string because people sometimes enter national prefix after country code, e.g
381       // +44 (0)20-1234-5678.
382       int indexOfPreviousNdd = prefixBeforeNationalNumber.lastIndexOf(nationalPrefixExtracted);
383       prefixBeforeNationalNumber.setLength(indexOfPreviousNdd);
384     }
385     return !nationalPrefixExtracted.equals(removeNationalPrefixFromNationalNumber());
386   }
387
388   private boolean isDigitOrLeadingPlusSign(char nextChar) {
389     return Character.isDigit(nextChar) ||
390         (accruedInput.length() == 1 &&
391          PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(Character.toString(nextChar)).matches());
392   }
393
394   String attemptToFormatAccruedDigits() {
395     for (NumberFormat numFormat : possibleFormats) {
396       Matcher m = regexCache.getPatternForRegex(numFormat.getPattern()).matcher(nationalNumber);
397       if (m.matches()) {
398         String formattedNumber = m.replaceAll(numFormat.getFormat());
399         return prefixBeforeNationalNumber + formattedNumber;
400       }
401     }
402     return "";
403   }
404
405   /**
406    * Returns the current position in the partially formatted phone number of the character which was
407    * previously passed in as the parameter of {@link #inputDigitAndRememberPosition}.
408    */
409   public int getRememberedPosition() {
410     if (!ableToFormat) {
411       return originalPosition;
412     }
413     int accruedInputIndex = 0, currentOutputIndex = 0;
414     while (accruedInputIndex < positionToRemember && currentOutputIndex < currentOutput.length()) {
415       if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
416           currentOutput.charAt(currentOutputIndex)) {
417         accruedInputIndex++;
418       }
419       currentOutputIndex++;
420     }
421     return currentOutputIndex;
422   }
423
424   // Attempts to set the formatting template and returns a string which contains the formatted
425   // version of the digits entered so far.
426   private String attemptToChooseFormattingPattern() {
427     // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH digits of national
428     // number (excluding national prefix) have been entered.
429     if (nationalNumber.length() >= MIN_LEADING_DIGITS_LENGTH) {
430       getAvailableFormats(nationalNumber.substring(0, MIN_LEADING_DIGITS_LENGTH));
431       maybeCreateNewTemplate();
432       return inputAccruedNationalNumber();
433     } else {
434       return prefixBeforeNationalNumber + nationalNumber.toString();
435     }
436   }
437
438   // Invokes inputDigitHelper on each digit of the national number accrued, and returns a formatted
439   // string in the end.
440   private String inputAccruedNationalNumber() {
441     int lengthOfNationalNumber = nationalNumber.length();
442     if (lengthOfNationalNumber > 0) {
443       String tempNationalNumber = "";
444       for (int i = 0; i < lengthOfNationalNumber; i++) {
445         tempNationalNumber = inputDigitHelper(nationalNumber.charAt(i));
446       }
447       return ableToFormat
448           ? prefixBeforeNationalNumber + tempNationalNumber
449           : tempNationalNumber;
450     } else {
451       return prefixBeforeNationalNumber.toString();
452     }
453   }
454
455   // Returns the national prefix extracted, or an empty string if it is not present.
456   private String removeNationalPrefixFromNationalNumber() {
457     int startOfNationalNumber = 0;
458     if (currentMetaData.getCountryCode() == 1 && nationalNumber.charAt(0) == '1') {
459       startOfNationalNumber = 1;
460       prefixBeforeNationalNumber.append("1 ");
461       isInternationalFormatting = true;
462     } else if (currentMetaData.hasNationalPrefixForParsing()) {
463       Pattern nationalPrefixForParsing =
464         regexCache.getPatternForRegex(currentMetaData.getNationalPrefixForParsing());
465       Matcher m = nationalPrefixForParsing.matcher(nationalNumber);
466       if (m.lookingAt()) {
467         // When the national prefix is detected, we use international formatting rules instead of
468         // national ones, because national formatting rules could contain local formatting rules
469         // for numbers entered without area code.
470         isInternationalFormatting = true;
471         startOfNationalNumber = m.end();
472         prefixBeforeNationalNumber.append(nationalNumber.substring(0, startOfNationalNumber));
473       }
474     }
475     String nationalPrefix = nationalNumber.substring(0, startOfNationalNumber);
476     nationalNumber.delete(0, startOfNationalNumber);
477     return nationalPrefix;
478   }
479
480   /**
481    * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are available, and places
482    * the remaining input into nationalNumber.
483    *
484    * @return  true when accruedInputWithoutFormatting begins with the plus sign or valid IDD for
485    *     defaultCountry.
486    */
487   private boolean attemptToExtractIdd() {
488     Pattern internationalPrefix =
489         regexCache.getPatternForRegex("\\" + PhoneNumberUtil.PLUS_SIGN + "|" +
490             currentMetaData.getInternationalPrefix());
491     Matcher iddMatcher = internationalPrefix.matcher(accruedInputWithoutFormatting);
492     if (iddMatcher.lookingAt()) {
493       isInternationalFormatting = true;
494       int startOfCountryCallingCode = iddMatcher.end();
495       nationalNumber.setLength(0);
496       nationalNumber.append(accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
497       prefixBeforeNationalNumber.setLength(0);
498       prefixBeforeNationalNumber.append(
499           accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
500       if (accruedInputWithoutFormatting.charAt(0) != PhoneNumberUtil.PLUS_SIGN) {
501         prefixBeforeNationalNumber.append(" ");
502       }
503       return true;
504     }
505     return false;
506   }
507
508   /**
509    * Extracts the country calling code from the beginning of nationalNumber to
510    * prefixBeforeNationalNumber when they are available, and places the remaining input into
511    * nationalNumber.
512    *
513    * @return  true when a valid country calling code can be found.
514    */
515   private boolean attemptToExtractCountryCallingCode() {
516     if (nationalNumber.length() == 0) {
517       return false;
518     }
519     StringBuilder numberWithoutCountryCallingCode = new StringBuilder();
520     int countryCode = phoneUtil.extractCountryCode(nationalNumber, numberWithoutCountryCallingCode);
521     if (countryCode == 0) {
522       return false;
523     }
524     nationalNumber.setLength(0);
525     nationalNumber.append(numberWithoutCountryCallingCode);
526     String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode);
527     if (!newRegionCode.equals(defaultCountry)) {
528       currentMetaData = getMetadataForRegion(newRegionCode);
529     }
530     String countryCodeString = Integer.toString(countryCode);
531     prefixBeforeNationalNumber.append(countryCodeString).append(" ");
532     return true;
533   }
534
535   // Accrues digits and the plus sign to accruedInputWithoutFormatting for later use. If nextChar
536   // contains a digit in non-ASCII format (e.g. the full-width version of digits), it is first
537   // normalized to the ASCII version. The return value is nextChar itself, or its normalized
538   // version, if nextChar is a digit in non-ASCII format. This method assumes its input is either a
539   // digit or the plus sign.
540   private char normalizeAndAccrueDigitsAndPlusSign(char nextChar, boolean rememberPosition) {
541     char normalizedChar;
542     if (nextChar == PhoneNumberUtil.PLUS_SIGN) {
543       normalizedChar = nextChar;
544       accruedInputWithoutFormatting.append(nextChar);
545     } else {
546       int radix = 10;
547       normalizedChar = Character.forDigit(Character.digit(nextChar, radix), radix);
548       accruedInputWithoutFormatting.append(normalizedChar);
549       nationalNumber.append(normalizedChar);
550     }
551     if (rememberPosition) {
552       positionToRemember = accruedInputWithoutFormatting.length();
553     }
554     return normalizedChar;
555   }
556
557   private String inputDigitHelper(char nextChar) {
558     Matcher digitMatcher = digitPattern.matcher(formattingTemplate);
559     if (digitMatcher.find(lastMatchPosition)) {
560       String tempTemplate = digitMatcher.replaceFirst(Character.toString(nextChar));
561       formattingTemplate.replace(0, tempTemplate.length(), tempTemplate);
562       lastMatchPosition = digitMatcher.start();
563       return formattingTemplate.substring(0, lastMatchPosition + 1);
564     } else {
565       if (possibleFormats.size() == 1) {
566         // More digits are entered than we could handle, and there are no other valid patterns to
567         // try.
568         ableToFormat = false;
569       }  // else, we just reset the formatting pattern.
570       currentFormattingPattern = "";
571       return accruedInput.toString();
572     }
573   }
574 }