• 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 
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