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