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