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