AsYouTypeFormatter.java revision 528b0d12a556ff0c2d16f99d56fcdd62657f433c
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
17package com.android.i18n.phonenumbers;
18
19import com.android.i18n.phonenumbers.Phonemetadata.NumberFormat;
20import com.android.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
21
22import java.util.ArrayList;
23import java.util.Iterator;
24import java.util.List;
25import java.util.regex.Matcher;
26import 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 */
41public 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             : accruedInput.toString();
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      return maybeCreateNewTemplate() ? inputAccruedNationalNumber() : accruedInput.toString();
432    } else {
433      return prefixBeforeNationalNumber + nationalNumber.toString();
434    }
435  }
436
437  // Invokes inputDigitHelper on each digit of the national number accrued, and returns a formatted
438  // string in the end.
439  private String inputAccruedNationalNumber() {
440    int lengthOfNationalNumber = nationalNumber.length();
441    if (lengthOfNationalNumber > 0) {
442      String tempNationalNumber = "";
443      for (int i = 0; i < lengthOfNationalNumber; i++) {
444        tempNationalNumber = inputDigitHelper(nationalNumber.charAt(i));
445      }
446      return ableToFormat
447          ? prefixBeforeNationalNumber + tempNationalNumber
448          : accruedInput.toString();
449    } else {
450      return prefixBeforeNationalNumber.toString();
451    }
452  }
453
454  // Returns the national prefix extracted, or an empty string if it is not present.
455  private String removeNationalPrefixFromNationalNumber() {
456    int startOfNationalNumber = 0;
457    if (currentMetaData.getCountryCode() == 1 && nationalNumber.charAt(0) == '1') {
458      startOfNationalNumber = 1;
459      prefixBeforeNationalNumber.append("1 ");
460      isInternationalFormatting = true;
461    } else if (currentMetaData.hasNationalPrefixForParsing()) {
462      Pattern nationalPrefixForParsing =
463        regexCache.getPatternForRegex(currentMetaData.getNationalPrefixForParsing());
464      Matcher m = nationalPrefixForParsing.matcher(nationalNumber);
465      if (m.lookingAt()) {
466        // When the national prefix is detected, we use international formatting rules instead of
467        // national ones, because national formatting rules could contain local formatting rules
468        // for numbers entered without area code.
469        isInternationalFormatting = true;
470        startOfNationalNumber = m.end();
471        prefixBeforeNationalNumber.append(nationalNumber.substring(0, startOfNationalNumber));
472      }
473    }
474    String nationalPrefix = nationalNumber.substring(0, startOfNationalNumber);
475    nationalNumber.delete(0, startOfNationalNumber);
476    return nationalPrefix;
477  }
478
479  /**
480   * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are available, and places
481   * the remaining input into nationalNumber.
482   *
483   * @return  true when accruedInputWithoutFormatting begins with the plus sign or valid IDD for
484   *     defaultCountry.
485   */
486  private boolean attemptToExtractIdd() {
487    Pattern internationalPrefix =
488        regexCache.getPatternForRegex("\\" + PhoneNumberUtil.PLUS_SIGN + "|" +
489            currentMetaData.getInternationalPrefix());
490    Matcher iddMatcher = internationalPrefix.matcher(accruedInputWithoutFormatting);
491    if (iddMatcher.lookingAt()) {
492      isInternationalFormatting = true;
493      int startOfCountryCallingCode = iddMatcher.end();
494      nationalNumber.setLength(0);
495      nationalNumber.append(accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
496      prefixBeforeNationalNumber.setLength(0);
497      prefixBeforeNationalNumber.append(
498          accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
499      if (accruedInputWithoutFormatting.charAt(0) != PhoneNumberUtil.PLUS_SIGN) {
500        prefixBeforeNationalNumber.append(" ");
501      }
502      return true;
503    }
504    return false;
505  }
506
507  /**
508   * Extracts the country calling code from the beginning of nationalNumber to
509   * prefixBeforeNationalNumber when they are available, and places the remaining input into
510   * nationalNumber.
511   *
512   * @return  true when a valid country calling code can be found.
513   */
514  private boolean attemptToExtractCountryCallingCode() {
515    if (nationalNumber.length() == 0) {
516      return false;
517    }
518    StringBuilder numberWithoutCountryCallingCode = new StringBuilder();
519    int countryCode = phoneUtil.extractCountryCode(nationalNumber, numberWithoutCountryCallingCode);
520    if (countryCode == 0) {
521      return false;
522    }
523    nationalNumber.setLength(0);
524    nationalNumber.append(numberWithoutCountryCallingCode);
525    String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode);
526    if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(newRegionCode)) {
527      currentMetaData = phoneUtil.getMetadataForNonGeographicalRegion(countryCode);
528    } else if (!newRegionCode.equals(defaultCountry)) {
529      currentMetaData = getMetadataForRegion(newRegionCode);
530    }
531    String countryCodeString = Integer.toString(countryCode);
532    prefixBeforeNationalNumber.append(countryCodeString).append(" ");
533    return true;
534  }
535
536  // Accrues digits and the plus sign to accruedInputWithoutFormatting for later use. If nextChar
537  // contains a digit in non-ASCII format (e.g. the full-width version of digits), it is first
538  // normalized to the ASCII version. The return value is nextChar itself, or its normalized
539  // version, if nextChar is a digit in non-ASCII format. This method assumes its input is either a
540  // digit or the plus sign.
541  private char normalizeAndAccrueDigitsAndPlusSign(char nextChar, boolean rememberPosition) {
542    char normalizedChar;
543    if (nextChar == PhoneNumberUtil.PLUS_SIGN) {
544      normalizedChar = nextChar;
545      accruedInputWithoutFormatting.append(nextChar);
546    } else {
547      int radix = 10;
548      normalizedChar = Character.forDigit(Character.digit(nextChar, radix), radix);
549      accruedInputWithoutFormatting.append(normalizedChar);
550      nationalNumber.append(normalizedChar);
551    }
552    if (rememberPosition) {
553      positionToRemember = accruedInputWithoutFormatting.length();
554    }
555    return normalizedChar;
556  }
557
558  private String inputDigitHelper(char nextChar) {
559    Matcher digitMatcher = digitPattern.matcher(formattingTemplate);
560    if (digitMatcher.find(lastMatchPosition)) {
561      String tempTemplate = digitMatcher.replaceFirst(Character.toString(nextChar));
562      formattingTemplate.replace(0, tempTemplate.length(), tempTemplate);
563      lastMatchPosition = digitMatcher.start();
564      return formattingTemplate.substring(0, lastMatchPosition + 1);
565    } else {
566      if (possibleFormats.size() == 1) {
567        // More digits are entered than we could handle, and there are no other valid patterns to
568        // try.
569        ableToFormat = false;
570      }  // else, we just reset the formatting pattern.
571      currentFormattingPattern = "";
572      return accruedInput.toString();
573    }
574  }
575}
576