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.android.i18n.phonenumbers; 18 19 import com.android.i18n.phonenumbers.Phonemetadata.NumberFormat; 20 import com.android.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 : 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