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