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