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