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 import com.google.i18n.phonenumbers.internal.RegexCache; 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 // This is set to true when we know the user is entering a full national significant number, since 54 // we have either detected a national prefix or an international dialing prefix. When this is 55 // true, we will no longer use local number formatting patterns. 56 private boolean isCompleteNumber = false; 57 private boolean isExpectingCountryCallingCode = false; 58 private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); 59 private String defaultCountry; 60 61 // Character used when appropriate to separate a prefix, such as a long NDD or a country calling 62 // code, from the national number. 63 private static final char SEPARATOR_BEFORE_NATIONAL_NUMBER = ' '; 64 private static final PhoneMetadata EMPTY_METADATA = 65 new PhoneMetadata().setInternationalPrefix("NA"); 66 private PhoneMetadata defaultMetadata; 67 private PhoneMetadata currentMetadata; 68 69 // A pattern that is used to determine if a numberFormat under availableFormats is eligible to be 70 // used by the AYTF. It is eligible when the format element under numberFormat contains groups of 71 // the dollar sign followed by a single digit, separated by valid phone number punctuation. This 72 // prevents invalid punctuation (such as the star sign in Israeli star numbers) getting into the 73 // output of the AYTF. 74 private static final Pattern ELIGIBLE_FORMAT_PATTERN = 75 Pattern.compile("[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*" 76 + "(\\$\\d" + "[" + PhoneNumberUtil.VALID_PUNCTUATION + "]*)+"); 77 // A set of characters that, if found in a national prefix formatting rules, are an indicator to 78 // us that we should separate the national prefix from the number when formatting. 79 private static final Pattern NATIONAL_PREFIX_SEPARATORS_PATTERN = Pattern.compile("[- ]"); 80 81 // This is the minimum length of national number accrued that is required to trigger the 82 // formatter. The first element of the leadingDigitsPattern of each numberFormat contains a 83 // regular expression that matches up to this number of digits. 84 private static final int MIN_LEADING_DIGITS_LENGTH = 3; 85 86 // The digits that have not been entered yet will be represented by a \u2008, the punctuation 87 // space. 88 private static final String DIGIT_PLACEHOLDER = "\u2008"; 89 private static final Pattern DIGIT_PATTERN = Pattern.compile(DIGIT_PLACEHOLDER); 90 private int lastMatchPosition = 0; 91 // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as 92 // found in the original sequence of characters the user entered. 93 private int originalPosition = 0; 94 // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as 95 // found in accruedInputWithoutFormatting. 96 private int positionToRemember = 0; 97 // This contains anything that has been entered so far preceding the national significant number, 98 // and it is formatted (e.g. with space inserted). For example, this can contain IDD, country 99 // code, and/or NDD, etc. 100 private StringBuilder prefixBeforeNationalNumber = new StringBuilder(); 101 private boolean shouldAddSpaceAfterNationalPrefix = false; 102 // This contains the national prefix that has been extracted. It contains only digits without 103 // formatting. 104 private String extractedNationalPrefix = ""; 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 */ AsYouTypeFormatter(String regionCode)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. getMetadataForRegion(String regionCode)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. maybeCreateNewTemplate()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 shouldAddSpaceAfterNationalPrefix = 151 NATIONAL_PREFIX_SEPARATORS_PATTERN.matcher( 152 numberFormat.getNationalPrefixFormattingRule()).find(); 153 // With a new formatting template, the matched position using the old template needs to be 154 // reset. 155 lastMatchPosition = 0; 156 return true; 157 } else { // Remove the current number format from possibleFormats. 158 it.remove(); 159 } 160 } 161 ableToFormat = false; 162 return false; 163 } 164 getAvailableFormats(String leadingDigits)165 private void getAvailableFormats(String leadingDigits) { 166 // First decide whether we should use international or national number rules. 167 boolean isInternationalNumber = isCompleteNumber && extractedNationalPrefix.length() == 0; 168 List<NumberFormat> formatList = 169 (isInternationalNumber && currentMetadata.intlNumberFormatSize() > 0) 170 ? currentMetadata.intlNumberFormats() 171 : currentMetadata.numberFormats(); 172 for (NumberFormat format : formatList) { 173 // Discard a few formats that we know are not relevant based on the presence of the national 174 // prefix. 175 if (extractedNationalPrefix.length() > 0 176 && PhoneNumberUtil.formattingRuleHasFirstGroupOnly( 177 format.getNationalPrefixFormattingRule()) 178 && !format.getNationalPrefixOptionalWhenFormatting() 179 && !format.hasDomesticCarrierCodeFormattingRule()) { 180 // If it is a national number that had a national prefix, any rules that aren't valid with a 181 // national prefix should be excluded. A rule that has a carrier-code formatting rule is 182 // kept since the national prefix might actually be an extracted carrier code - we don't 183 // distinguish between these when extracting it in the AYTF. 184 continue; 185 } else if (extractedNationalPrefix.length() == 0 186 && !isCompleteNumber 187 && !PhoneNumberUtil.formattingRuleHasFirstGroupOnly( 188 format.getNationalPrefixFormattingRule()) 189 && !format.getNationalPrefixOptionalWhenFormatting()) { 190 // This number was entered without a national prefix, and this formatting rule requires one, 191 // so we discard it. 192 continue; 193 } 194 if (ELIGIBLE_FORMAT_PATTERN.matcher(format.getFormat()).matches()) { 195 possibleFormats.add(format); 196 } 197 } 198 narrowDownPossibleFormats(leadingDigits); 199 } 200 narrowDownPossibleFormats(String leadingDigits)201 private void narrowDownPossibleFormats(String leadingDigits) { 202 int indexOfLeadingDigitsPattern = leadingDigits.length() - MIN_LEADING_DIGITS_LENGTH; 203 Iterator<NumberFormat> it = possibleFormats.iterator(); 204 while (it.hasNext()) { 205 NumberFormat format = it.next(); 206 if (format.leadingDigitsPatternSize() == 0) { 207 // Keep everything that isn't restricted by leading digits. 208 continue; 209 } 210 int lastLeadingDigitsPattern = 211 Math.min(indexOfLeadingDigitsPattern, format.leadingDigitsPatternSize() - 1); 212 Pattern leadingDigitsPattern = regexCache.getPatternForRegex( 213 format.getLeadingDigitsPattern(lastLeadingDigitsPattern)); 214 Matcher m = leadingDigitsPattern.matcher(leadingDigits); 215 if (!m.lookingAt()) { 216 it.remove(); 217 } 218 } 219 } 220 createFormattingTemplate(NumberFormat format)221 private boolean createFormattingTemplate(NumberFormat format) { 222 String numberPattern = format.getPattern(); 223 formattingTemplate.setLength(0); 224 String tempTemplate = getFormattingTemplate(numberPattern, format.getFormat()); 225 if (tempTemplate.length() > 0) { 226 formattingTemplate.append(tempTemplate); 227 return true; 228 } 229 return false; 230 } 231 232 // Gets a formatting template which can be used to efficiently format a partial number where 233 // digits are added one by one. getFormattingTemplate(String numberPattern, String numberFormat)234 private String getFormattingTemplate(String numberPattern, String numberFormat) { 235 // Creates a phone number consisting only of the digit 9 that matches the 236 // numberPattern by applying the pattern to the longestPhoneNumber string. 237 String longestPhoneNumber = "999999999999999"; 238 Matcher m = regexCache.getPatternForRegex(numberPattern).matcher(longestPhoneNumber); 239 m.find(); // this will always succeed 240 String aPhoneNumber = m.group(); 241 // No formatting template can be created if the number of digits entered so far is longer than 242 // the maximum the current formatting rule can accommodate. 243 if (aPhoneNumber.length() < nationalNumber.length()) { 244 return ""; 245 } 246 // Formats the number according to numberFormat 247 String template = aPhoneNumber.replaceAll(numberPattern, numberFormat); 248 // Replaces each digit with character DIGIT_PLACEHOLDER 249 template = template.replaceAll("9", DIGIT_PLACEHOLDER); 250 return template; 251 } 252 253 /** 254 * Clears the internal state of the formatter, so it can be reused. 255 */ clear()256 public void clear() { 257 currentOutput = ""; 258 accruedInput.setLength(0); 259 accruedInputWithoutFormatting.setLength(0); 260 formattingTemplate.setLength(0); 261 lastMatchPosition = 0; 262 currentFormattingPattern = ""; 263 prefixBeforeNationalNumber.setLength(0); 264 extractedNationalPrefix = ""; 265 nationalNumber.setLength(0); 266 ableToFormat = true; 267 inputHasFormatting = false; 268 positionToRemember = 0; 269 originalPosition = 0; 270 isCompleteNumber = false; 271 isExpectingCountryCallingCode = false; 272 possibleFormats.clear(); 273 shouldAddSpaceAfterNationalPrefix = false; 274 if (!currentMetadata.equals(defaultMetadata)) { 275 currentMetadata = getMetadataForRegion(defaultCountry); 276 } 277 } 278 279 /** 280 * Formats a phone number on-the-fly as each digit is entered. 281 * 282 * @param nextChar the most recently entered digit of a phone number. Formatting characters are 283 * allowed, but as soon as they are encountered this method formats the number as entered and 284 * not "as you type" anymore. Full width digits and Arabic-indic digits are allowed, and will 285 * be shown as they are. 286 * @return the partially formatted phone number. 287 */ inputDigit(char nextChar)288 public String inputDigit(char nextChar) { 289 currentOutput = inputDigitWithOptionToRememberPosition(nextChar, false); 290 return currentOutput; 291 } 292 293 /** 294 * Same as {@link #inputDigit}, but remembers the position where {@code nextChar} is inserted, so 295 * that it can be retrieved later by using {@link #getRememberedPosition}. The remembered 296 * position will be automatically adjusted if additional formatting characters are later 297 * inserted/removed in front of {@code nextChar}. 298 */ inputDigitAndRememberPosition(char nextChar)299 public String inputDigitAndRememberPosition(char nextChar) { 300 currentOutput = inputDigitWithOptionToRememberPosition(nextChar, true); 301 return currentOutput; 302 } 303 304 @SuppressWarnings("fallthrough") inputDigitWithOptionToRememberPosition(char nextChar, boolean rememberPosition)305 private String inputDigitWithOptionToRememberPosition(char nextChar, boolean rememberPosition) { 306 accruedInput.append(nextChar); 307 if (rememberPosition) { 308 originalPosition = accruedInput.length(); 309 } 310 // We do formatting on-the-fly only when each character entered is either a digit, or a plus 311 // sign (accepted at the start of the number only). 312 if (!isDigitOrLeadingPlusSign(nextChar)) { 313 ableToFormat = false; 314 inputHasFormatting = true; 315 } else { 316 nextChar = normalizeAndAccrueDigitsAndPlusSign(nextChar, rememberPosition); 317 } 318 if (!ableToFormat) { 319 // When we are unable to format because of reasons other than that formatting chars have been 320 // entered, it can be due to really long IDDs or NDDs. If that is the case, we might be able 321 // to do formatting again after extracting them. 322 if (inputHasFormatting) { 323 return accruedInput.toString(); 324 } else if (attemptToExtractIdd()) { 325 if (attemptToExtractCountryCallingCode()) { 326 return attemptToChoosePatternWithPrefixExtracted(); 327 } 328 } else if (ableToExtractLongerNdd()) { 329 // Add an additional space to separate long NDD and national significant number for 330 // readability. We don't set shouldAddSpaceAfterNationalPrefix to true, since we don't want 331 // this to change later when we choose formatting templates. 332 prefixBeforeNationalNumber.append(SEPARATOR_BEFORE_NATIONAL_NUMBER); 333 return attemptToChoosePatternWithPrefixExtracted(); 334 } 335 return accruedInput.toString(); 336 } 337 338 // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH digits (the plus 339 // sign is counted as a digit as well for this purpose) have been entered. 340 switch (accruedInputWithoutFormatting.length()) { 341 case 0: 342 case 1: 343 case 2: 344 return accruedInput.toString(); 345 case 3: 346 if (attemptToExtractIdd()) { 347 isExpectingCountryCallingCode = true; 348 } else { // No IDD or plus sign is found, might be entering in national format. 349 extractedNationalPrefix = removeNationalPrefixFromNationalNumber(); 350 return attemptToChooseFormattingPattern(); 351 } 352 // fall through 353 default: 354 if (isExpectingCountryCallingCode) { 355 if (attemptToExtractCountryCallingCode()) { 356 isExpectingCountryCallingCode = false; 357 } 358 return prefixBeforeNationalNumber + nationalNumber.toString(); 359 } 360 if (possibleFormats.size() > 0) { // The formatting patterns are already chosen. 361 String tempNationalNumber = inputDigitHelper(nextChar); 362 // See if the accrued digits can be formatted properly already. If not, use the results 363 // from inputDigitHelper, which does formatting based on the formatting pattern chosen. 364 String formattedNumber = attemptToFormatAccruedDigits(); 365 if (formattedNumber.length() > 0) { 366 return formattedNumber; 367 } 368 narrowDownPossibleFormats(nationalNumber.toString()); 369 if (maybeCreateNewTemplate()) { 370 return inputAccruedNationalNumber(); 371 } 372 return ableToFormat 373 ? appendNationalNumber(tempNationalNumber) 374 : accruedInput.toString(); 375 } else { 376 return attemptToChooseFormattingPattern(); 377 } 378 } 379 } 380 attemptToChoosePatternWithPrefixExtracted()381 private String attemptToChoosePatternWithPrefixExtracted() { 382 ableToFormat = true; 383 isExpectingCountryCallingCode = false; 384 possibleFormats.clear(); 385 lastMatchPosition = 0; 386 formattingTemplate.setLength(0); 387 currentFormattingPattern = ""; 388 return attemptToChooseFormattingPattern(); 389 } 390 391 // @VisibleForTesting getExtractedNationalPrefix()392 String getExtractedNationalPrefix() { 393 return extractedNationalPrefix; 394 } 395 396 // Some national prefixes are a substring of others. If extracting the shorter NDD doesn't result 397 // in a number we can format, we try to see if we can extract a longer version here. ableToExtractLongerNdd()398 private boolean ableToExtractLongerNdd() { 399 if (extractedNationalPrefix.length() > 0) { 400 // Put the extracted NDD back to the national number before attempting to extract a new NDD. 401 nationalNumber.insert(0, extractedNationalPrefix); 402 // Remove the previously extracted NDD from prefixBeforeNationalNumber. We cannot simply set 403 // it to empty string because people sometimes incorrectly enter national prefix after the 404 // country code, e.g. +44 (0)20-1234-5678. 405 int indexOfPreviousNdd = prefixBeforeNationalNumber.lastIndexOf(extractedNationalPrefix); 406 prefixBeforeNationalNumber.setLength(indexOfPreviousNdd); 407 } 408 return !extractedNationalPrefix.equals(removeNationalPrefixFromNationalNumber()); 409 } 410 isDigitOrLeadingPlusSign(char nextChar)411 private boolean isDigitOrLeadingPlusSign(char nextChar) { 412 return Character.isDigit(nextChar) 413 || (accruedInput.length() == 1 414 && PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(Character.toString(nextChar)).matches()); 415 } 416 417 /** 418 * Checks to see if there is an exact pattern match for these digits. If so, we should use this 419 * instead of any other formatting template whose leadingDigitsPattern also matches the input. 420 */ attemptToFormatAccruedDigits()421 String attemptToFormatAccruedDigits() { 422 for (NumberFormat numberFormat : possibleFormats) { 423 Matcher m = regexCache.getPatternForRegex(numberFormat.getPattern()).matcher(nationalNumber); 424 if (m.matches()) { 425 shouldAddSpaceAfterNationalPrefix = 426 NATIONAL_PREFIX_SEPARATORS_PATTERN.matcher( 427 numberFormat.getNationalPrefixFormattingRule()).find(); 428 String formattedNumber = m.replaceAll(numberFormat.getFormat()); 429 // Check that we did not remove nor add any extra digits when we matched 430 // this formatting pattern. This usually happens after we entered the last 431 // digit during AYTF. Eg: In case of MX, we swallow mobile token (1) when 432 // formatted but AYTF should retain all the number entered and not change 433 // in order to match a format (of same leading digits and length) display 434 // in that way. 435 String fullOutput = appendNationalNumber(formattedNumber); 436 String formattedNumberDigitsOnly = PhoneNumberUtil.normalizeDiallableCharsOnly(fullOutput); 437 if (formattedNumberDigitsOnly.contentEquals(accruedInputWithoutFormatting)) { 438 // If it's the same (i.e entered number and format is same), then it's 439 // safe to return this in formatted number as nothing is lost / added. 440 return fullOutput; 441 } 442 } 443 } 444 return ""; 445 } 446 447 /** 448 * Returns the current position in the partially formatted phone number of the character which was 449 * previously passed in as the parameter of {@link #inputDigitAndRememberPosition}. 450 */ getRememberedPosition()451 public int getRememberedPosition() { 452 if (!ableToFormat) { 453 return originalPosition; 454 } 455 int accruedInputIndex = 0; 456 int currentOutputIndex = 0; 457 while (accruedInputIndex < positionToRemember && currentOutputIndex < currentOutput.length()) { 458 if (accruedInputWithoutFormatting.charAt(accruedInputIndex) 459 == currentOutput.charAt(currentOutputIndex)) { 460 accruedInputIndex++; 461 } 462 currentOutputIndex++; 463 } 464 return currentOutputIndex; 465 } 466 467 /** 468 * Combines the national number with any prefix (IDD/+ and country code or national prefix) that 469 * was collected. A space will be inserted between them if the current formatting template 470 * indicates this to be suitable. 471 */ appendNationalNumber(String nationalNumber)472 private String appendNationalNumber(String nationalNumber) { 473 int prefixBeforeNationalNumberLength = prefixBeforeNationalNumber.length(); 474 if (shouldAddSpaceAfterNationalPrefix && prefixBeforeNationalNumberLength > 0 475 && prefixBeforeNationalNumber.charAt(prefixBeforeNationalNumberLength - 1) 476 != SEPARATOR_BEFORE_NATIONAL_NUMBER) { 477 // We want to add a space after the national prefix if the national prefix formatting rule 478 // indicates that this would normally be done, with the exception of the case where we already 479 // appended a space because the NDD was surprisingly long. 480 return new String(prefixBeforeNationalNumber) + SEPARATOR_BEFORE_NATIONAL_NUMBER 481 + nationalNumber; 482 } else { 483 return prefixBeforeNationalNumber + nationalNumber; 484 } 485 } 486 487 /** 488 * Attempts to set the formatting template and returns a string which contains the formatted 489 * version of the digits entered so far. 490 */ attemptToChooseFormattingPattern()491 private String attemptToChooseFormattingPattern() { 492 // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH digits of national 493 // number (excluding national prefix) have been entered. 494 if (nationalNumber.length() >= MIN_LEADING_DIGITS_LENGTH) { 495 496 getAvailableFormats(nationalNumber.toString()); 497 // See if the accrued digits can be formatted properly already. 498 String formattedNumber = attemptToFormatAccruedDigits(); 499 if (formattedNumber.length() > 0) { 500 return formattedNumber; 501 } 502 return maybeCreateNewTemplate() ? inputAccruedNationalNumber() : accruedInput.toString(); 503 } else { 504 return appendNationalNumber(nationalNumber.toString()); 505 } 506 } 507 508 /** 509 * Invokes inputDigitHelper on each digit of the national number accrued, and returns a formatted 510 * string in the end. 511 */ inputAccruedNationalNumber()512 private String inputAccruedNationalNumber() { 513 int lengthOfNationalNumber = nationalNumber.length(); 514 if (lengthOfNationalNumber > 0) { 515 String tempNationalNumber = ""; 516 for (int i = 0; i < lengthOfNationalNumber; i++) { 517 tempNationalNumber = inputDigitHelper(nationalNumber.charAt(i)); 518 } 519 return ableToFormat ? appendNationalNumber(tempNationalNumber) : accruedInput.toString(); 520 } else { 521 return prefixBeforeNationalNumber.toString(); 522 } 523 } 524 525 /** 526 * Returns true if the current country is a NANPA country and the national number begins with 527 * the national prefix. 528 */ isNanpaNumberWithNationalPrefix()529 private boolean isNanpaNumberWithNationalPrefix() { 530 // For NANPA numbers beginning with 1[2-9], treat the 1 as the national prefix. The reason is 531 // that national significant numbers in NANPA always start with [2-9] after the national prefix. 532 // Numbers beginning with 1[01] can only be short/emergency numbers, which don't need the 533 // national prefix. 534 return (currentMetadata.getCountryCode() == 1) && (nationalNumber.charAt(0) == '1') 535 && (nationalNumber.charAt(1) != '0') && (nationalNumber.charAt(1) != '1'); 536 } 537 538 // Returns the national prefix extracted, or an empty string if it is not present. removeNationalPrefixFromNationalNumber()539 private String removeNationalPrefixFromNationalNumber() { 540 int startOfNationalNumber = 0; 541 if (isNanpaNumberWithNationalPrefix()) { 542 startOfNationalNumber = 1; 543 prefixBeforeNationalNumber.append('1').append(SEPARATOR_BEFORE_NATIONAL_NUMBER); 544 isCompleteNumber = true; 545 } else if (currentMetadata.hasNationalPrefixForParsing()) { 546 Pattern nationalPrefixForParsing = 547 regexCache.getPatternForRegex(currentMetadata.getNationalPrefixForParsing()); 548 Matcher m = nationalPrefixForParsing.matcher(nationalNumber); 549 // Since some national prefix patterns are entirely optional, check that a national prefix 550 // could actually be extracted. 551 if (m.lookingAt() && m.end() > 0) { 552 // When the national prefix is detected, we use international formatting rules instead of 553 // national ones, because national formatting rules could contain local formatting rules 554 // for numbers entered without area code. 555 isCompleteNumber = true; 556 startOfNationalNumber = m.end(); 557 prefixBeforeNationalNumber.append(nationalNumber.substring(0, startOfNationalNumber)); 558 } 559 } 560 String nationalPrefix = nationalNumber.substring(0, startOfNationalNumber); 561 nationalNumber.delete(0, startOfNationalNumber); 562 return nationalPrefix; 563 } 564 565 /** 566 * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are available, and places 567 * the remaining input into nationalNumber. 568 * 569 * @return true when accruedInputWithoutFormatting begins with the plus sign or valid IDD for 570 * defaultCountry. 571 */ attemptToExtractIdd()572 private boolean attemptToExtractIdd() { 573 Pattern internationalPrefix = 574 regexCache.getPatternForRegex("\\" + PhoneNumberUtil.PLUS_SIGN + "|" 575 + currentMetadata.getInternationalPrefix()); 576 Matcher iddMatcher = internationalPrefix.matcher(accruedInputWithoutFormatting); 577 if (iddMatcher.lookingAt()) { 578 isCompleteNumber = true; 579 int startOfCountryCallingCode = iddMatcher.end(); 580 nationalNumber.setLength(0); 581 nationalNumber.append(accruedInputWithoutFormatting.substring(startOfCountryCallingCode)); 582 prefixBeforeNationalNumber.setLength(0); 583 prefixBeforeNationalNumber.append( 584 accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode)); 585 if (accruedInputWithoutFormatting.charAt(0) != PhoneNumberUtil.PLUS_SIGN) { 586 prefixBeforeNationalNumber.append(SEPARATOR_BEFORE_NATIONAL_NUMBER); 587 } 588 return true; 589 } 590 return false; 591 } 592 593 /** 594 * Extracts the country calling code from the beginning of nationalNumber to 595 * prefixBeforeNationalNumber when they are available, and places the remaining input into 596 * nationalNumber. 597 * 598 * @return true when a valid country calling code can be found. 599 */ attemptToExtractCountryCallingCode()600 private boolean attemptToExtractCountryCallingCode() { 601 if (nationalNumber.length() == 0) { 602 return false; 603 } 604 StringBuilder numberWithoutCountryCallingCode = new StringBuilder(); 605 int countryCode = phoneUtil.extractCountryCode(nationalNumber, numberWithoutCountryCallingCode); 606 if (countryCode == 0) { 607 return false; 608 } 609 nationalNumber.setLength(0); 610 nationalNumber.append(numberWithoutCountryCallingCode); 611 String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode); 612 if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(newRegionCode)) { 613 currentMetadata = phoneUtil.getMetadataForNonGeographicalRegion(countryCode); 614 } else if (!newRegionCode.equals(defaultCountry)) { 615 currentMetadata = getMetadataForRegion(newRegionCode); 616 } 617 String countryCodeString = Integer.toString(countryCode); 618 prefixBeforeNationalNumber.append(countryCodeString).append(SEPARATOR_BEFORE_NATIONAL_NUMBER); 619 // When we have successfully extracted the IDD, the previously extracted NDD should be cleared 620 // because it is no longer valid. 621 extractedNationalPrefix = ""; 622 return true; 623 } 624 625 // Accrues digits and the plus sign to accruedInputWithoutFormatting for later use. If nextChar 626 // contains a digit in non-ASCII format (e.g. the full-width version of digits), it is first 627 // normalized to the ASCII version. The return value is nextChar itself, or its normalized 628 // version, if nextChar is a digit in non-ASCII format. This method assumes its input is either a 629 // digit or the plus sign. normalizeAndAccrueDigitsAndPlusSign(char nextChar, boolean rememberPosition)630 private char normalizeAndAccrueDigitsAndPlusSign(char nextChar, boolean rememberPosition) { 631 char normalizedChar; 632 if (nextChar == PhoneNumberUtil.PLUS_SIGN) { 633 normalizedChar = nextChar; 634 accruedInputWithoutFormatting.append(nextChar); 635 } else { 636 int radix = 10; 637 normalizedChar = Character.forDigit(Character.digit(nextChar, radix), radix); 638 accruedInputWithoutFormatting.append(normalizedChar); 639 nationalNumber.append(normalizedChar); 640 } 641 if (rememberPosition) { 642 positionToRemember = accruedInputWithoutFormatting.length(); 643 } 644 return normalizedChar; 645 } 646 inputDigitHelper(char nextChar)647 private String inputDigitHelper(char nextChar) { 648 // Note that formattingTemplate is not guaranteed to have a value, it could be empty, e.g. 649 // when the next digit is entered after extracting an IDD or NDD. 650 Matcher digitMatcher = DIGIT_PATTERN.matcher(formattingTemplate); 651 if (digitMatcher.find(lastMatchPosition)) { 652 String tempTemplate = digitMatcher.replaceFirst(Character.toString(nextChar)); 653 formattingTemplate.replace(0, tempTemplate.length(), tempTemplate); 654 lastMatchPosition = digitMatcher.start(); 655 return formattingTemplate.substring(0, lastMatchPosition + 1); 656 } else { 657 if (possibleFormats.size() == 1) { 658 // More digits are entered than we could handle, and there are no other valid patterns to 659 // try. 660 ableToFormat = false; 661 } // else, we just reset the formatting pattern. 662 currentFormattingPattern = ""; 663 return accruedInput.toString(); 664 } 665 } 666 } 667