/* * Copyright (C) 2009 The Libphonenumber Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.i18n.phonenumbers; import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat; import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility for international phone numbers. Functionality includes formatting, parsing and * validation. * *
If you use this library, and want to be notified about important changes, please sign up to
* our mailing list.
*
* NOTE: A lot of methods in this class require Region Code strings. These must be provided using
* ISO 3166-1 two-letter country-code format. These should be in upper-case. The list of the codes
* can be found here:
* http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm
*
* @author Shaopeng Jia
*/
public class PhoneNumberUtil {
// @VisibleForTesting
static final MetadataLoader DEFAULT_METADATA_LOADER = new MetadataLoader() {
@Override
public InputStream loadMetadata(String metadataFileName) {
return PhoneNumberUtil.class.getResourceAsStream(metadataFileName);
}
};
private static final Logger logger = Logger.getLogger(PhoneNumberUtil.class.getName());
/** Flags to use when compiling regular expressions for phone numbers. */
static final int REGEX_FLAGS = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
// The minimum and maximum length of the national significant number.
private static final int MIN_LENGTH_FOR_NSN = 2;
// The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
static final int MAX_LENGTH_FOR_NSN = 17;
// The maximum length of the country calling code.
static final int MAX_LENGTH_COUNTRY_CODE = 3;
// We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
// input from overflowing the regular-expression engine.
private static final int MAX_INPUT_STRING_LENGTH = 250;
private static final String META_DATA_FILE_PREFIX =
"/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto";
// Region-code for the unknown region.
private static final String UNKNOWN_REGION = "ZZ";
private static final int NANPA_COUNTRY_CODE = 1;
// The prefix that needs to be inserted in front of a Colombian landline number when dialed from
// a mobile phone in Colombia.
private static final String COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = "3";
// Map of country calling codes that use a mobile token before the area code. One example of when
// this is relevant is when determining the length of the national destination code, which should
// be the length of the area code plus the length of the mobile token.
private static final Map
* Warning: This level might result in lower coverage especially for regions outside of country
* code "+1". If you are not sure about which level to use, email the discussion group
* libphonenumber-discuss@googlegroups.com.
*/
STRICT_GROUPING {
@Override
boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
if (!util.isValidNumber(number) ||
!PhoneNumberMatcher.containsOnlyValidXChars(number, candidate, util) ||
PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate) ||
!PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util)) {
return false;
}
return PhoneNumberMatcher.checkNumberGroupingIsValid(
number, candidate, util, new PhoneNumberMatcher.NumberGroupingChecker() {
@Override
public boolean checkGroups(PhoneNumberUtil util, PhoneNumber number,
StringBuilder normalizedCandidate,
String[] expectedNumberGroups) {
return PhoneNumberMatcher.allNumberGroupsRemainGrouped(
util, number, normalizedCandidate, expectedNumberGroups);
}
});
}
},
/**
* Phone numbers accepted are {@linkplain PhoneNumberUtil#isValidNumber(PhoneNumber) valid} and
* are grouped in the same way that we would have formatted it, or as a single block. For
* example, a US number written as "650 2530000" is not accepted at this leniency level, whereas
* "650 253 0000" or "6502530000" are.
* Numbers with more than one '/' symbol are also dropped at this level.
*
* Warning: This level might result in lower coverage especially for regions outside of country
* code "+1". If you are not sure about which level to use, email the discussion group
* libphonenumber-discuss@googlegroups.com.
*/
EXACT_GROUPING {
@Override
boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
if (!util.isValidNumber(number) ||
!PhoneNumberMatcher.containsOnlyValidXChars(number, candidate, util) ||
PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate) ||
!PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util)) {
return false;
}
return PhoneNumberMatcher.checkNumberGroupingIsValid(
number, candidate, util, new PhoneNumberMatcher.NumberGroupingChecker() {
@Override
public boolean checkGroups(PhoneNumberUtil util, PhoneNumber number,
StringBuilder normalizedCandidate,
String[] expectedNumberGroups) {
return PhoneNumberMatcher.allNumberGroupsAreExactlyPresent(
util, number, normalizedCandidate, expectedNumberGroups);
}
});
}
};
/** Returns true if {@code number} is a verified number according to this leniency. */
abstract boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util);
}
// A mapping from a country calling code to the region codes which denote the region represented
// by that country calling code. In the case of multiple regions sharing a calling code, such as
// the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
// first.
private final Map The {@link PhoneNumberUtil} is implemented as a singleton. Therefore, calling getInstance
* multiple times will only result in one instance being created.
*
* @return a PhoneNumberUtil instance
*/
public static synchronized PhoneNumberUtil getInstance() {
if (instance == null) {
setInstance(createInstance(DEFAULT_METADATA_LOADER));
}
return instance;
}
/**
* Create a new {@link PhoneNumberUtil} instance to carry out international phone number
* formatting, parsing, or validation. The instance is loaded with all metadata by
* using the metadataLoader specified.
*
* This method should only be used in the rare case in which you want to manage your own
* metadata loading. Calling this method multiple times is very expensive, as each time
* a new instance is created from scratch. When in doubt, use {@link #getInstance}.
*
* @param metadataLoader Customized metadata loader. If null, default metadata loader will
* be used. This should not be null.
* @return a PhoneNumberUtil instance
*/
public static PhoneNumberUtil createInstance(MetadataLoader metadataLoader) {
if (metadataLoader == null) {
throw new IllegalArgumentException("metadataLoader could not be null.");
}
return new PhoneNumberUtil(META_DATA_FILE_PREFIX, metadataLoader,
CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap());
}
/**
* Helper function to check if the national prefix formatting rule has the first group only, i.e.,
* does not start with the national prefix.
*/
static boolean formattingRuleHasFirstGroupOnly(String nationalPrefixFormattingRule) {
return nationalPrefixFormattingRule.length() == 0 ||
FIRST_GROUP_ONLY_PREFIX_PATTERN.matcher(nationalPrefixFormattingRule).matches();
}
/**
* Tests whether a phone number has a geographical association. It checks if the number is
* associated to a certain region in the country where it belongs to. Note that this doesn't
* verify if the number is actually in use.
*
* A similar method is implemented as PhoneNumberOfflineGeocoder.canBeGeocoded, which performs a
* looser check, since it only prevents cases where prefixes overlap for geocodable and
* non-geocodable numbers. Also, if new phone number types were added, we should check if this
* other method should be updated too.
*/
boolean isNumberGeographical(PhoneNumber phoneNumber) {
PhoneNumberType numberType = getNumberType(phoneNumber);
// TODO: Include mobile phone numbers from countries like Indonesia, which has some
// mobile numbers that are geographical.
return numberType == PhoneNumberType.FIXED_LINE ||
numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE;
}
/**
* Helper function to check region code is not unknown or null.
*/
private boolean isValidRegionCode(String regionCode) {
return regionCode != null && supportedRegions.contains(regionCode);
}
/**
* Helper function to check the country calling code is valid.
*/
private boolean hasValidCountryCallingCode(int countryCallingCode) {
return countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode);
}
/**
* Formats a phone number in the specified format using default rules. Note that this does not
* promise to produce a phone number that the user can dial from where they are - although we do
* format in either 'national' or 'international' format depending on what the client asks for, we
* do not currently support a more abbreviated format, such as for users in the same "area" who
* could potentially dial the number without area code. Note that if the phone number has a
* country calling code of 0 or an otherwise invalid country calling code, we cannot work out
* which formatting rules to apply so we return the national significant number with no formatting
* applied.
*
* @param number the phone number to be formatted
* @param numberFormat the format the phone number should be formatted into
* @return the formatted phone number
*/
public String format(PhoneNumber number, PhoneNumberFormat numberFormat) {
if (number.getNationalNumber() == 0 && number.hasRawInput()) {
// Unparseable numbers that kept their raw input just use that.
// This is the only case where a number can be formatted as E164 without a
// leading '+' symbol (but the original number wasn't parseable anyway).
// TODO: Consider removing the 'if' above so that unparseable
// strings without raw input format to the empty string instead of "+00"
String rawInput = number.getRawInput();
if (rawInput.length() > 0) {
return rawInput;
}
}
StringBuilder formattedNumber = new StringBuilder(20);
format(number, numberFormat, formattedNumber);
return formattedNumber.toString();
}
/**
* Same as {@link #format(PhoneNumber, PhoneNumberFormat)}, but accepts a mutable StringBuilder as
* a parameter to decrease object creation when invoked many times.
*/
public void format(PhoneNumber number, PhoneNumberFormat numberFormat,
StringBuilder formattedNumber) {
// Clear the StringBuilder first.
formattedNumber.setLength(0);
int countryCallingCode = number.getCountryCode();
String nationalSignificantNumber = getNationalSignificantNumber(number);
if (numberFormat == PhoneNumberFormat.E164) {
// Early exit for E164 case (even if the country calling code is invalid) since no formatting
// of the national number needs to be applied. Extensions are not formatted.
formattedNumber.append(nationalSignificantNumber);
prefixNumberWithCountryCallingCode(countryCallingCode, PhoneNumberFormat.E164,
formattedNumber);
return;
}
if (!hasValidCountryCallingCode(countryCallingCode)) {
formattedNumber.append(nationalSignificantNumber);
return;
}
// Note getRegionCodeForCountryCode() is used because formatting information for regions which
// share a country calling code is contained by only one region for performance reasons. For
// example, for NANPA regions it will be contained in the metadata for US.
String regionCode = getRegionCodeForCountryCode(countryCallingCode);
// Metadata cannot be null because the country calling code is valid (which means that the
// region code cannot be ZZ and must be one of our supported region codes).
PhoneMetadata metadata =
getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
formattedNumber.append(formatNsn(nationalSignificantNumber, metadata, numberFormat));
maybeAppendFormattedExtension(number, metadata, numberFormat, formattedNumber);
prefixNumberWithCountryCallingCode(countryCallingCode, numberFormat, formattedNumber);
}
/**
* Formats a phone number in the specified format using client-defined formatting rules. Note that
* if the phone number has a country calling code of zero or an otherwise invalid country calling
* code, we cannot work out things like whether there should be a national prefix applied, or how
* to format extensions, so we return the national significant number with no formatting applied.
*
* @param number the phone number to be formatted
* @param numberFormat the format the phone number should be formatted into
* @param userDefinedFormats formatting rules specified by clients
* @return the formatted phone number
*/
public String formatByPattern(PhoneNumber number,
PhoneNumberFormat numberFormat,
List Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier code passed in
* should take precedence over the number's {@code preferredDomesticCarrierCode} when formatting.
*
* @param number the phone number to be formatted
* @param fallbackCarrierCode the carrier selection code to be used, if none is found in the
* phone number itself
* @return the formatted phone number in national format for dialing using the number's
* {@code preferredDomesticCarrierCode}, or the {@code fallbackCarrierCode} passed in if
* none is found
*/
public String formatNationalNumberWithPreferredCarrierCode(PhoneNumber number,
String fallbackCarrierCode) {
return formatNationalNumberWithCarrierCode(number, number.hasPreferredDomesticCarrierCode()
? number.getPreferredDomesticCarrierCode()
: fallbackCarrierCode);
}
/**
* Returns a number formatted in such a way that it can be dialed from a mobile phone in a
* specific region. If the number cannot be reached from the region (e.g. some countries block
* toll-free numbers from being called outside of the country), the method returns an empty
* string.
*
* @param number the phone number to be formatted
* @param regionCallingFrom the region where the call is being placed
* @param withFormatting whether the number should be returned with formatting symbols, such as
* spaces and dashes.
* @return the formatted phone number
*/
public String formatNumberForMobileDialing(PhoneNumber number, String regionCallingFrom,
boolean withFormatting) {
int countryCallingCode = number.getCountryCode();
if (!hasValidCountryCallingCode(countryCallingCode)) {
return number.hasRawInput() ? number.getRawInput() : "";
}
String formattedNumber = "";
// Clear the extension, as that part cannot normally be dialed together with the main number.
PhoneNumber numberNoExt = new PhoneNumber().mergeFrom(number).clearExtension();
String regionCode = getRegionCodeForCountryCode(countryCallingCode);
PhoneNumberType numberType = getNumberType(numberNoExt);
boolean isValidNumber = (numberType != PhoneNumberType.UNKNOWN);
if (regionCallingFrom.equals(regionCode)) {
boolean isFixedLineOrMobile =
(numberType == PhoneNumberType.FIXED_LINE) || (numberType == PhoneNumberType.MOBILE) ||
(numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
// Carrier codes may be needed in some countries. We handle this here.
if (regionCode.equals("CO") && numberType == PhoneNumberType.FIXED_LINE) {
formattedNumber =
formatNationalNumberWithCarrierCode(numberNoExt, COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX);
} else if (regionCode.equals("BR") && isFixedLineOrMobile) {
formattedNumber = numberNoExt.hasPreferredDomesticCarrierCode()
? formattedNumber = formatNationalNumberWithPreferredCarrierCode(numberNoExt, "")
// Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
// called within Brazil. Without that, most of the carriers won't connect the call.
// Because of that, we return an empty string here.
: "";
} else if (isValidNumber && regionCode.equals("HU")) {
// The national format for HU numbers doesn't contain the national prefix, because that is
// how numbers are normally written down. However, the national prefix is obligatory when
// dialing from a mobile phone, except for short numbers. As a result, we add it back here
// if it is a valid regular length phone number.
formattedNumber =
getNddPrefixForRegion(regionCode, true /* strip non-digits */) +
" " + format(numberNoExt, PhoneNumberFormat.NATIONAL);
} else if (countryCallingCode == NANPA_COUNTRY_CODE) {
// For NANPA countries, we output international format for numbers that can be dialed
// internationally, since that always works, except for numbers which might potentially be
// short numbers, which are always dialled in national format.
PhoneMetadata regionMetadata = getMetadataForRegion(regionCallingFrom);
if (canBeInternationallyDialled(numberNoExt) &&
!isShorterThanPossibleNormalNumber(regionMetadata,
getNationalSignificantNumber(numberNoExt))) {
formattedNumber = format(numberNoExt, PhoneNumberFormat.INTERNATIONAL);
} else {
formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
}
} else {
// For non-geographical countries, and Mexican and Chilean fixed line and mobile numbers, we
// output international format for numbers that can be dialed internationally as that always
// works.
if ((regionCode.equals(REGION_CODE_FOR_NON_GEO_ENTITY) ||
// MX fixed line and mobile numbers should always be formatted in international format,
// even when dialed within MX. For national format to work, a carrier code needs to be
// used, and the correct carrier code depends on if the caller and callee are from the
// same local area. It is trickier to get that to work correctly than using
// international format, which is tested to work fine on all carriers.
// CL fixed line numbers need the national prefix when dialing in the national format,
// but don't have it when used for display. The reverse is true for mobile numbers.
// As a result, we output them in the international format to make it work.
((regionCode.equals("MX") || regionCode.equals("CL")) &&
isFixedLineOrMobile)) &&
canBeInternationallyDialled(numberNoExt)) {
formattedNumber = format(numberNoExt, PhoneNumberFormat.INTERNATIONAL);
} else {
formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
}
}
} else if (isValidNumber && canBeInternationallyDialled(numberNoExt)) {
// We assume that short numbers are not diallable from outside their region, so if a number
// is not a valid regular length phone number, we treat it as if it cannot be internationally
// dialled.
return withFormatting ? format(numberNoExt, PhoneNumberFormat.INTERNATIONAL)
: format(numberNoExt, PhoneNumberFormat.E164);
}
return withFormatting ? formattedNumber
: normalizeDiallableCharsOnly(formattedNumber);
}
/**
* Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
* supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
* same as that of the region where the number is from, then NATIONAL formatting will be applied.
*
* If the number itself has a country calling code of zero or an otherwise invalid country
* calling code, then we return the number with no formatting applied.
*
* Note this function takes care of the case for calling inside of NANPA and between Russia and
* Kazakhstan (who share the same country calling code). In those cases, no international prefix
* is used. For regions which have multiple international prefixes, the number in its
* INTERNATIONAL format will be returned instead.
*
* @param number the phone number to be formatted
* @param regionCallingFrom the region where the call is being placed
* @return the formatted phone number
*/
public String formatOutOfCountryCallingNumber(PhoneNumber number,
String regionCallingFrom) {
if (!isValidRegionCode(regionCallingFrom)) {
logger.log(Level.WARNING,
"Trying to format number from invalid region "
+ regionCallingFrom
+ ". International formatting applied.");
return format(number, PhoneNumberFormat.INTERNATIONAL);
}
int countryCallingCode = number.getCountryCode();
String nationalSignificantNumber = getNationalSignificantNumber(number);
if (!hasValidCountryCallingCode(countryCallingCode)) {
return nationalSignificantNumber;
}
if (countryCallingCode == NANPA_COUNTRY_CODE) {
if (isNANPACountry(regionCallingFrom)) {
// For NANPA regions, return the national format for these regions but prefix it with the
// country calling code.
return countryCallingCode + " " + format(number, PhoneNumberFormat.NATIONAL);
}
} else if (countryCallingCode == getCountryCodeForValidRegion(regionCallingFrom)) {
// If regions share a country calling code, the country calling code need not be dialled.
// This also applies when dialling within a region, so this if clause covers both these cases.
// Technically this is the case for dialling from La Reunion to other overseas departments of
// France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
// edge case for now and for those cases return the version including country calling code.
// Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
return format(number, PhoneNumberFormat.NATIONAL);
}
// Metadata cannot be null because we checked 'isValidRegionCode()' above.
PhoneMetadata metadataForRegionCallingFrom = getMetadataForRegion(regionCallingFrom);
String internationalPrefix = metadataForRegionCallingFrom.getInternationalPrefix();
// For regions that have multiple international prefixes, the international format of the
// number is returned, unless there is a preferred international prefix.
String internationalPrefixForFormatting = "";
if (UNIQUE_INTERNATIONAL_PREFIX.matcher(internationalPrefix).matches()) {
internationalPrefixForFormatting = internationalPrefix;
} else if (metadataForRegionCallingFrom.hasPreferredInternationalPrefix()) {
internationalPrefixForFormatting =
metadataForRegionCallingFrom.getPreferredInternationalPrefix();
}
String regionCode = getRegionCodeForCountryCode(countryCallingCode);
// Metadata cannot be null because the country calling code is valid.
PhoneMetadata metadataForRegion =
getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
String formattedNationalNumber =
formatNsn(nationalSignificantNumber, metadataForRegion, PhoneNumberFormat.INTERNATIONAL);
StringBuilder formattedNumber = new StringBuilder(formattedNationalNumber);
maybeAppendFormattedExtension(number, metadataForRegion, PhoneNumberFormat.INTERNATIONAL,
formattedNumber);
if (internationalPrefixForFormatting.length() > 0) {
formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, " ")
.insert(0, internationalPrefixForFormatting);
} else {
prefixNumberWithCountryCallingCode(countryCallingCode,
PhoneNumberFormat.INTERNATIONAL,
formattedNumber);
}
return formattedNumber.toString();
}
/**
* Formats a phone number using the original phone number format that the number is parsed from.
* The original format is embedded in the country_code_source field of the PhoneNumber object
* passed in. If such information is missing, the number will be formatted into the NATIONAL
* format by default. When the number contains a leading zero and this is unexpected for this
* country, or we don't have a formatting pattern for the number, the method returns the raw input
* when it is available.
*
* Note this method guarantees no digit will be inserted, removed or modified as a result of
* formatting.
*
* @param number the phone number that needs to be formatted in its original number format
* @param regionCallingFrom the region whose IDD needs to be prefixed if the original number
* has one
* @return the formatted phone number in its original number format
*/
public String formatInOriginalFormat(PhoneNumber number, String regionCallingFrom) {
if (number.hasRawInput() &&
(hasUnexpectedItalianLeadingZero(number) || !hasFormattingPatternForNumber(number))) {
// We check if we have the formatting pattern because without that, we might format the number
// as a group without national prefix.
return number.getRawInput();
}
if (!number.hasCountryCodeSource()) {
return format(number, PhoneNumberFormat.NATIONAL);
}
String formattedNumber;
switch (number.getCountryCodeSource()) {
case FROM_NUMBER_WITH_PLUS_SIGN:
formattedNumber = format(number, PhoneNumberFormat.INTERNATIONAL);
break;
case FROM_NUMBER_WITH_IDD:
formattedNumber = formatOutOfCountryCallingNumber(number, regionCallingFrom);
break;
case FROM_NUMBER_WITHOUT_PLUS_SIGN:
formattedNumber = format(number, PhoneNumberFormat.INTERNATIONAL).substring(1);
break;
case FROM_DEFAULT_COUNTRY:
// Fall-through to default case.
default:
String regionCode = getRegionCodeForCountryCode(number.getCountryCode());
// We strip non-digits from the NDD here, and from the raw input later, so that we can
// compare them easily.
String nationalPrefix = getNddPrefixForRegion(regionCode, true /* strip non-digits */);
String nationalFormat = format(number, PhoneNumberFormat.NATIONAL);
if (nationalPrefix == null || nationalPrefix.length() == 0) {
// If the region doesn't have a national prefix at all, we can safely return the national
// format without worrying about a national prefix being added.
formattedNumber = nationalFormat;
break;
}
// Otherwise, we check if the original number was entered with a national prefix.
if (rawInputContainsNationalPrefix(
number.getRawInput(), nationalPrefix, regionCode)) {
// If so, we can safely return the national format.
formattedNumber = nationalFormat;
break;
}
// Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
// there is no metadata for the region.
PhoneMetadata metadata = getMetadataForRegion(regionCode);
String nationalNumber = getNationalSignificantNumber(number);
NumberFormat formatRule =
chooseFormattingPatternForNumber(metadata.numberFormats(), nationalNumber);
// The format rule could still be null here if the national number was 0 and there was no
// raw input (this should not be possible for numbers generated by the phonenumber library
// as they would also not have a country calling code and we would have exited earlier).
if (formatRule == null) {
formattedNumber = nationalFormat;
break;
}
// When the format we apply to this number doesn't contain national prefix, we can just
// return the national format.
// TODO: Refactor the code below with the code in
// isNationalPrefixPresentIfRequired.
String candidateNationalPrefixRule = formatRule.getNationalPrefixFormattingRule();
// We assume that the first-group symbol will never be _before_ the national prefix.
int indexOfFirstGroup = candidateNationalPrefixRule.indexOf("$1");
if (indexOfFirstGroup <= 0) {
formattedNumber = nationalFormat;
break;
}
candidateNationalPrefixRule =
candidateNationalPrefixRule.substring(0, indexOfFirstGroup);
candidateNationalPrefixRule = normalizeDigitsOnly(candidateNationalPrefixRule);
if (candidateNationalPrefixRule.length() == 0) {
// National prefix not used when formatting this number.
formattedNumber = nationalFormat;
break;
}
// Otherwise, we need to remove the national prefix from our output.
NumberFormat numFormatCopy = new NumberFormat();
numFormatCopy.mergeFrom(formatRule);
numFormatCopy.clearNationalPrefixFormattingRule();
List Caveats: Warning: Do not use this method for do-your-own formatting - for some regions, the
* national dialling prefix is used only for certain types of numbers. Use the library's
* formatting functions to prefix the national prefix when required.
*
* @param regionCode the region that we want to get the dialling prefix for
* @param stripNonDigits true to strip non-digits from the national dialling prefix
* @return the dialling prefix for the region denoted by regionCode
*/
public String getNddPrefixForRegion(String regionCode, boolean stripNonDigits) {
PhoneMetadata metadata = getMetadataForRegion(regionCode);
if (metadata == null) {
logger.log(Level.WARNING,
"Invalid or missing region code ("
+ ((regionCode == null) ? "null" : regionCode)
+ ") provided.");
return null;
}
String nationalPrefix = metadata.getNationalPrefix();
// If no national prefix was found, we return null.
if (nationalPrefix.length() == 0) {
return null;
}
if (stripNonDigits) {
// Note: if any other non-numeric symbols are ever used in national prefixes, these would have
// to be removed here as well.
nationalPrefix = nationalPrefix.replace("~", "");
}
return nationalPrefix;
}
/**
* Checks if this is a region under the North American Numbering Plan Administration (NANPA).
*
* @return true if regionCode is one of the regions under NANPA
*/
public boolean isNANPACountry(String regionCode) {
return nanpaRegions.contains(regionCode);
}
/**
* Checks whether the country calling code is from a region whose national significant number
* could contain a leading zero. An example of such a region is Italy. Returns false if no
* metadata for the country is found.
*/
boolean isLeadingZeroPossible(int countryCallingCode) {
PhoneMetadata mainMetadataForCallingCode =
getMetadataForRegionOrCallingCode(countryCallingCode,
getRegionCodeForCountryCode(countryCallingCode));
if (mainMetadataForCallingCode == null) {
return false;
}
return mainMetadataForCallingCode.isLeadingZeroPossible();
}
/**
* Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT. A valid vanity
* number will start with at least 3 digits and will have three or more alpha characters. This
* does not do region-specific checks - to work out if this number is actually valid for a region,
* it should be parsed and methods such as {@link #isPossibleNumberWithReason} and
* {@link #isValidNumber} should be used.
*
* @param number the number that needs to be checked
* @return true if the number is a valid vanity number
*/
public boolean isAlphaNumber(String number) {
if (!isViablePhoneNumber(number)) {
// Number is too short, or doesn't match the basic phone number pattern.
return false;
}
StringBuilder strippedNumber = new StringBuilder(number);
maybeStripExtension(strippedNumber);
return VALID_ALPHA_PHONE_PATTERN.matcher(strippedNumber).matches();
}
/**
* Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of returning the reason
* for failure, this method returns a boolean value.
* @param number the number that needs to be checked
* @return true if the number is possible
*/
public boolean isPossibleNumber(PhoneNumber number) {
return isPossibleNumberWithReason(number) == ValidationResult.IS_POSSIBLE;
}
/**
* Helper method to check a number against a particular pattern and determine whether it matches,
* or is too short or too long. Currently, if a number pattern suggests that numbers of length 7
* and 10 are possible, and a number in between these possible lengths is entered, such as of
* length 8, this will return TOO_LONG.
*/
private ValidationResult testNumberLengthAgainstPattern(Pattern numberPattern, String number) {
Matcher numberMatcher = numberPattern.matcher(number);
if (numberMatcher.matches()) {
return ValidationResult.IS_POSSIBLE;
}
if (numberMatcher.lookingAt()) {
return ValidationResult.TOO_LONG;
} else {
return ValidationResult.TOO_SHORT;
}
}
/**
* Helper method to check whether a number is too short to be a regular length phone number in a
* region.
*/
private boolean isShorterThanPossibleNormalNumber(PhoneMetadata regionMetadata, String number) {
Pattern possibleNumberPattern = regexCache.getPatternForRegex(
regionMetadata.getGeneralDesc().getPossibleNumberPattern());
return testNumberLengthAgainstPattern(possibleNumberPattern, number) ==
ValidationResult.TOO_SHORT;
}
/**
* Check whether a phone number is a possible number. It provides a more lenient check than
* {@link #isValidNumber} in the following sense:
* This method first parses the number, then invokes {@link #isPossibleNumber(PhoneNumber)}
* with the resultant PhoneNumber object.
*
* @param number the number that needs to be checked, in the form of a string
* @param regionDialingFrom the region that we are expecting the number to be dialed from.
* Note this is different from the region where the number belongs. For example, the number
* +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
* dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
* region which uses an international dialling prefix of 00. When it is written as
* 650 253 0000, it can only be dialed from within the US, and when written as 253 0000, it
* can only be dialed from within a smaller area in the US (Mountain View, CA, to be more
* specific).
* @return true if the number is possible
*/
public boolean isPossibleNumber(String number, String regionDialingFrom) {
try {
return isPossibleNumber(parse(number, regionDialingFrom));
} catch (NumberParseException e) {
return false;
}
}
/**
* Attempts to extract a valid number from a phone number that is too long to be valid, and resets
* the PhoneNumber object passed in to that valid version. If no valid number could be extracted,
* the PhoneNumber object passed in will not be modified.
* @param number a PhoneNumber object which contains a number that is too long to be valid.
* @return true if a valid phone number can be successfully extracted.
*/
public boolean truncateTooLongNumber(PhoneNumber number) {
if (isValidNumber(number)) {
return true;
}
PhoneNumber numberCopy = new PhoneNumber();
numberCopy.mergeFrom(number);
long nationalNumber = number.getNationalNumber();
do {
nationalNumber /= 10;
numberCopy.setNationalNumber(nationalNumber);
if (isPossibleNumberWithReason(numberCopy) == ValidationResult.TOO_SHORT ||
nationalNumber == 0) {
return false;
}
} while (!isValidNumber(numberCopy));
number.setNationalNumber(nationalNumber);
return true;
}
/**
* Gets an {@link com.google.i18n.phonenumbers.AsYouTypeFormatter} for the specific region.
*
* @param regionCode the region where the phone number is being entered
* @return an {@link com.google.i18n.phonenumbers.AsYouTypeFormatter} object, which can be used
* to format phone numbers in the specific region "as you type"
*/
public AsYouTypeFormatter getAsYouTypeFormatter(String regionCode) {
return new AsYouTypeFormatter(regionCode);
}
// Extracts country calling code from fullNumber, returns it and places the remaining number in
// nationalNumber. It assumes that the leading plus sign or IDD has already been removed. Returns
// 0 if fullNumber doesn't start with a valid country calling code, and leaves nationalNumber
// unmodified.
int extractCountryCode(StringBuilder fullNumber, StringBuilder nationalNumber) {
if ((fullNumber.length() == 0) || (fullNumber.charAt(0) == '0')) {
// Country codes do not begin with a '0'.
return 0;
}
int potentialCountryCode;
int numberLength = fullNumber.length();
for (int i = 1; i <= MAX_LENGTH_COUNTRY_CODE && i <= numberLength; i++) {
potentialCountryCode = Integer.parseInt(fullNumber.substring(0, i));
if (countryCallingCodeToRegionCodeMap.containsKey(potentialCountryCode)) {
nationalNumber.append(fullNumber.substring(i));
return potentialCountryCode;
}
}
return 0;
}
/**
* Tries to extract a country calling code from a number. This method will return zero if no
* country calling code is considered to be present. Country calling codes are extracted in the
* following ways:
* Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero for Italian numbers
* and any extension present are the same.
* Returns NSN_MATCH if either or both has no region specified, and the NSNs and extensions are
* the same.
* Returns SHORT_NSN_MATCH if either or both has no region specified, or the region specified is
* the same, and one NSN could be a shorter version of the other number. This includes the case
* where one has an extension specified, and the other does not.
* Returns NO_MATCH otherwise.
* For example, the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH.
* The numbers +1 345 657 1234 and 345 657 are a NO_MATCH.
*
* @param firstNumberIn first number to compare
* @param secondNumberIn second number to compare
*
* @return NO_MATCH, SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of equality
* of the two numbers, described in the method definition.
*/
public MatchType isNumberMatch(PhoneNumber firstNumberIn, PhoneNumber secondNumberIn) {
// Make copies of the phone number so that the numbers passed in are not edited.
PhoneNumber firstNumber = new PhoneNumber();
firstNumber.mergeFrom(firstNumberIn);
PhoneNumber secondNumber = new PhoneNumber();
secondNumber.mergeFrom(secondNumberIn);
// First clear raw_input, country_code_source and preferred_domestic_carrier_code fields and any
// empty-string extensions so that we can use the proto-buffer equality method.
firstNumber.clearRawInput();
firstNumber.clearCountryCodeSource();
firstNumber.clearPreferredDomesticCarrierCode();
secondNumber.clearRawInput();
secondNumber.clearCountryCodeSource();
secondNumber.clearPreferredDomesticCarrierCode();
if (firstNumber.hasExtension() &&
firstNumber.getExtension().length() == 0) {
firstNumber.clearExtension();
}
if (secondNumber.hasExtension() &&
secondNumber.getExtension().length() == 0) {
secondNumber.clearExtension();
}
// Early exit if both had extensions and these are different.
if (firstNumber.hasExtension() && secondNumber.hasExtension() &&
!firstNumber.getExtension().equals(secondNumber.getExtension())) {
return MatchType.NO_MATCH;
}
int firstNumberCountryCode = firstNumber.getCountryCode();
int secondNumberCountryCode = secondNumber.getCountryCode();
// Both had country_code specified.
if (firstNumberCountryCode != 0 && secondNumberCountryCode != 0) {
if (firstNumber.exactlySameAs(secondNumber)) {
return MatchType.EXACT_MATCH;
} else if (firstNumberCountryCode == secondNumberCountryCode &&
isNationalNumberSuffixOfTheOther(firstNumber, secondNumber)) {
// A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of
// an 'Italian leading zero', the presence or absence of an extension, or one NSN being a
// shorter variant of the other.
return MatchType.SHORT_NSN_MATCH;
}
// This is not a match.
return MatchType.NO_MATCH;
}
// Checks cases where one or both country_code fields were not specified. To make equality
// checks easier, we first set the country_code fields to be equal.
firstNumber.setCountryCode(secondNumberCountryCode);
// If all else was the same, then this is an NSN_MATCH.
if (firstNumber.exactlySameAs(secondNumber)) {
return MatchType.NSN_MATCH;
}
if (isNationalNumberSuffixOfTheOther(firstNumber, secondNumber)) {
return MatchType.SHORT_NSN_MATCH;
}
return MatchType.NO_MATCH;
}
// Returns true when one national number is the suffix of the other or both are the same.
private boolean isNationalNumberSuffixOfTheOther(PhoneNumber firstNumber,
PhoneNumber secondNumber) {
String firstNumberNationalNumber = String.valueOf(firstNumber.getNationalNumber());
String secondNumberNationalNumber = String.valueOf(secondNumber.getNationalNumber());
// Note that endsWith returns true if the numbers are equal.
return firstNumberNationalNumber.endsWith(secondNumberNationalNumber) ||
secondNumberNationalNumber.endsWith(firstNumberNationalNumber);
}
/**
* Takes two phone numbers as strings and compares them for equality. This is a convenience
* wrapper for {@link #isNumberMatch(PhoneNumber, PhoneNumber)}. No default region is known.
*
* @param firstNumber first number to compare. Can contain formatting, and can have country
* calling code specified with + at the start.
* @param secondNumber second number to compare. Can contain formatting, and can have country
* calling code specified with + at the start.
* @return NOT_A_NUMBER, NO_MATCH, SHORT_NSN_MATCH, NSN_MATCH, EXACT_MATCH. See
* {@link #isNumberMatch(PhoneNumber, PhoneNumber)} for more details.
*/
public MatchType isNumberMatch(String firstNumber, String secondNumber) {
try {
PhoneNumber firstNumberAsProto = parse(firstNumber, UNKNOWN_REGION);
return isNumberMatch(firstNumberAsProto, secondNumber);
} catch (NumberParseException e) {
if (e.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
try {
PhoneNumber secondNumberAsProto = parse(secondNumber, UNKNOWN_REGION);
return isNumberMatch(secondNumberAsProto, firstNumber);
} catch (NumberParseException e2) {
if (e2.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
try {
PhoneNumber firstNumberProto = new PhoneNumber();
PhoneNumber secondNumberProto = new PhoneNumber();
parseHelper(firstNumber, null, false, false, firstNumberProto);
parseHelper(secondNumber, null, false, false, secondNumberProto);
return isNumberMatch(firstNumberProto, secondNumberProto);
} catch (NumberParseException e3) {
// Fall through and return MatchType.NOT_A_NUMBER.
}
}
}
}
}
// One or more of the phone numbers we are trying to match is not a viable phone number.
return MatchType.NOT_A_NUMBER;
}
/**
* Takes two phone numbers and compares them for equality. This is a convenience wrapper for
* {@link #isNumberMatch(PhoneNumber, PhoneNumber)}. No default region is known.
*
* @param firstNumber first number to compare in proto buffer format.
* @param secondNumber second number to compare. Can contain formatting, and can have country
* calling code specified with + at the start.
* @return NOT_A_NUMBER, NO_MATCH, SHORT_NSN_MATCH, NSN_MATCH, EXACT_MATCH. See
* {@link #isNumberMatch(PhoneNumber, PhoneNumber)} for more details.
*/
public MatchType isNumberMatch(PhoneNumber firstNumber, String secondNumber) {
// First see if the second number has an implicit country calling code, by attempting to parse
// it.
try {
PhoneNumber secondNumberAsProto = parse(secondNumber, UNKNOWN_REGION);
return isNumberMatch(firstNumber, secondNumberAsProto);
} catch (NumberParseException e) {
if (e.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
// The second number has no country calling code. EXACT_MATCH is no longer possible.
// We parse it as if the region was the same as that for the first number, and if
// EXACT_MATCH is returned, we replace this with NSN_MATCH.
String firstNumberRegion = getRegionCodeForCountryCode(firstNumber.getCountryCode());
try {
if (!firstNumberRegion.equals(UNKNOWN_REGION)) {
PhoneNumber secondNumberWithFirstNumberRegion = parse(secondNumber, firstNumberRegion);
MatchType match = isNumberMatch(firstNumber, secondNumberWithFirstNumberRegion);
if (match == MatchType.EXACT_MATCH) {
return MatchType.NSN_MATCH;
}
return match;
} else {
// If the first number didn't have a valid country calling code, then we parse the
// second number without one as well.
PhoneNumber secondNumberProto = new PhoneNumber();
parseHelper(secondNumber, null, false, false, secondNumberProto);
return isNumberMatch(firstNumber, secondNumberProto);
}
} catch (NumberParseException e2) {
// Fall-through to return NOT_A_NUMBER.
}
}
}
// One or more of the phone numbers we are trying to match is not a viable phone number.
return MatchType.NOT_A_NUMBER;
}
/**
* Returns true if the number can be dialled from outside the region, or unknown. If the number
* can only be dialled from within the region, returns false. Does not check the number is a valid
* number. Note that, at the moment, this method does not handle short numbers.
* TODO: Make this method public when we have enough metadata to make it worthwhile.
*
* @param number the phone-number for which we want to know whether it is diallable from
* outside the region
*/
// @VisibleForTesting
boolean canBeInternationallyDialled(PhoneNumber number) {
PhoneMetadata metadata = getMetadataForRegion(getRegionCodeForNumber(number));
if (metadata == null) {
// Note numbers belonging to non-geographical entities (e.g. +800 numbers) are always
// internationally diallable, and will be caught here.
return true;
}
String nationalSignificantNumber = getNationalSignificantNumber(number);
return !isNumberMatchingDesc(nationalSignificantNumber, metadata.getNoInternationalDialling());
}
/**
* Returns true if the supplied region supports mobile number portability. Returns false for
* invalid, unknown or regions that don't support mobile number portability.
*
* @param regionCode the region for which we want to know whether it supports mobile number
* portability or not.
*/
public boolean isMobileNumberPortableRegion(String regionCode) {
PhoneMetadata metadata = getMetadataForRegion(regionCode);
if (metadata == null) {
logger.log(Level.WARNING, "Invalid or unknown region code provided: " + regionCode);
return false;
}
return metadata.isMobileNumberPortableRegion();
}
}
* PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
* PhoneNumber number = phoneUtil.parse("16502530000", "US");
* String nationalSignificantNumber = phoneUtil.getNationalSignificantNumber(number);
* String areaCode;
* String subscriberNumber;
*
* int areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
* if (areaCodeLength > 0) {
* areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
* subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
* } else {
* areaCode = "";
* subscriberNumber = nationalSignificantNumber;
* }
*
*
* N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
* using it for most purposes, but recommends using the more general {@code national_number}
* instead. Read the following carefully before deciding to use this method:
*
*
* @param number the PhoneNumber object for which clients
* want to know the length of the area code.
* @return the length of area code of the PhoneNumber object
* passed in.
*/
public int getLengthOfGeographicalAreaCode(PhoneNumber number) {
PhoneMetadata metadata = getMetadataForRegion(getRegionCodeForNumber(number));
if (metadata == null) {
return 0;
}
// If a country doesn't use a national prefix, and this number doesn't have an Italian leading
// zero, we assume it is a closed dialling plan with no area codes.
if (!metadata.hasNationalPrefix() && !number.isItalianLeadingZero()) {
return 0;
}
if (!isNumberGeographical(number)) {
return 0;
}
return getLengthOfNationalDestinationCode(number);
}
/**
* Gets the length of the national destination code (NDC) from the
* PhoneNumber object passed in, so that clients could use it
* to split a national significant number into NDC and subscriber number. The NDC of a phone
* number is normally the first group of digit(s) right after the country calling code when the
* number is formatted in the international format, if there is a subscriber number part that
* follows. An example of how this could be used:
*
*
* PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
* PhoneNumber number = phoneUtil.parse("18002530000", "US");
* String nationalSignificantNumber = phoneUtil.getNationalSignificantNumber(number);
* String nationalDestinationCode;
* String subscriberNumber;
*
* int nationalDestinationCodeLength = phoneUtil.getLengthOfNationalDestinationCode(number);
* if (nationalDestinationCodeLength > 0) {
* nationalDestinationCode = nationalSignificantNumber.substring(0,
* nationalDestinationCodeLength);
* subscriberNumber = nationalSignificantNumber.substring(nationalDestinationCodeLength);
* } else {
* nationalDestinationCode = "";
* subscriberNumber = nationalSignificantNumber;
* }
*
*
* Refer to the unittests to see the difference between this function and
* {@link #getLengthOfGeographicalAreaCode}.
*
* @param number the PhoneNumber object for which clients
* want to know the length of the NDC.
* @return the length of NDC of the PhoneNumber object
* passed in.
*/
public int getLengthOfNationalDestinationCode(PhoneNumber number) {
PhoneNumber copiedProto;
if (number.hasExtension()) {
// We don't want to alter the proto given to us, but we don't want to include the extension
// when we format it, so we copy it and clear the extension here.
copiedProto = new PhoneNumber();
copiedProto.mergeFrom(number);
copiedProto.clearExtension();
} else {
copiedProto = number;
}
String nationalSignificantNumber = format(copiedProto,
PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
String[] numberGroups = NON_DIGITS_PATTERN.split(nationalSignificantNumber);
// The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty
// string (before the + symbol) and the second group will be the country calling code. The third
// group will be area code if it is not the last group.
if (numberGroups.length <= 3) {
return 0;
}
if (getNumberType(number) == PhoneNumberType.MOBILE) {
// For example Argentinian mobile numbers, when formatted in the international format, are in
// the form of +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and
// add the length of the second group (which is the mobile token), which also forms part of
// the national significant number. This assumes that the mobile token is always formatted
// separately from the rest of the phone number.
String mobileToken = getCountryMobileToken(number.getCountryCode());
if (!mobileToken.equals("")) {
return numberGroups[2].length() + numberGroups[3].length();
}
}
return numberGroups[2].length();
}
/**
* Returns the mobile token for the provided country calling code if it has one, otherwise
* returns an empty string. A mobile token is a number inserted before the area code when dialing
* a mobile number from that country from abroad.
*
* @param countryCallingCode the country calling code for which we want the mobile token
* @return the mobile token, as a string, for the given country calling code
*/
public static String getCountryMobileToken(int countryCallingCode) {
if (MOBILE_TOKEN_MAPPINGS.containsKey(countryCallingCode)) {
return MOBILE_TOKEN_MAPPINGS.get(countryCallingCode);
}
return "";
}
/**
* Normalizes a string of characters representing a phone number by replacing all characters found
* in the accompanying map with the values therein, and stripping all other characters if
* removeNonMatches is true.
*
* @param number a string of characters representing a phone number
* @param normalizationReplacements a mapping of characters to what they should be replaced by in
* the normalized version of the phone number
* @param removeNonMatches indicates whether characters that are not able to be replaced
* should be stripped from the number. If this is false, they
* will be left unchanged in the number.
* @return the normalized string version of the phone number
*/
private static String normalizeHelper(String number,
Map
*
*
* @param number the phone number that needs to be formatted
* @param regionCallingFrom the region where the call is being placed
* @return the formatted phone number
*/
public String formatOutOfCountryKeepingAlphaChars(PhoneNumber number,
String regionCallingFrom) {
String rawInput = number.getRawInput();
// If there is no raw input, then we can't keep alpha characters because there aren't any.
// In this case, we return formatOutOfCountryCallingNumber.
if (rawInput.length() == 0) {
return formatOutOfCountryCallingNumber(number, regionCallingFrom);
}
int countryCode = number.getCountryCode();
if (!hasValidCountryCallingCode(countryCode)) {
return rawInput;
}
// Strip any prefix such as country calling code, IDD, that was present. We do this by comparing
// the number in raw_input with the parsed number.
// To do this, first we normalize punctuation. We retain number grouping symbols such as " "
// only.
rawInput = normalizeHelper(rawInput, ALL_PLUS_NUMBER_GROUPING_SYMBOLS, true);
// Now we trim everything before the first three digits in the parsed number. We choose three
// because all valid alpha numbers have 3 digits at the start - if it does not, then we don't
// trim anything at all. Similarly, if the national number was less than three digits, we don't
// trim anything at all.
String nationalNumber = getNationalSignificantNumber(number);
if (nationalNumber.length() > 3) {
int firstNationalNumberDigit = rawInput.indexOf(nationalNumber.substring(0, 3));
if (firstNationalNumberDigit != -1) {
rawInput = rawInput.substring(firstNationalNumberDigit);
}
}
PhoneMetadata metadataForRegionCallingFrom = getMetadataForRegion(regionCallingFrom);
if (countryCode == NANPA_COUNTRY_CODE) {
if (isNANPACountry(regionCallingFrom)) {
return countryCode + " " + rawInput;
}
} else if (metadataForRegionCallingFrom != null &&
countryCode == getCountryCodeForValidRegion(regionCallingFrom)) {
NumberFormat formattingPattern =
chooseFormattingPatternForNumber(metadataForRegionCallingFrom.numberFormats(),
nationalNumber);
if (formattingPattern == null) {
// If no pattern above is matched, we format the original input.
return rawInput;
}
NumberFormat newFormat = new NumberFormat();
newFormat.mergeFrom(formattingPattern);
// The first group is the first group of digits that the user wrote together.
newFormat.setPattern("(\\d+)(.*)");
// Here we just concatenate them back together after the national prefix has been fixed.
newFormat.setFormat("$1$2");
// Now we format using this pattern instead of the default pattern, but with the national
// prefix prefixed if necessary.
// This will not work in the cases where the pattern (and not the leading digits) decide
// whether a national prefix needs to be used, since we have overridden the pattern to match
// anything, but that is not the case in the metadata to date.
return formatNsnUsingPattern(rawInput, newFormat, PhoneNumberFormat.NATIONAL);
}
String internationalPrefixForFormatting = "";
// If an unsupported region-calling-from is entered, or a country with multiple international
// prefixes, the international format of the number is returned, unless there is a preferred
// international prefix.
if (metadataForRegionCallingFrom != null) {
String internationalPrefix = metadataForRegionCallingFrom.getInternationalPrefix();
internationalPrefixForFormatting =
UNIQUE_INTERNATIONAL_PREFIX.matcher(internationalPrefix).matches()
? internationalPrefix
: metadataForRegionCallingFrom.getPreferredInternationalPrefix();
}
StringBuilder formattedNumber = new StringBuilder(rawInput);
String regionCode = getRegionCodeForCountryCode(countryCode);
// Metadata cannot be null because the country calling code is valid.
PhoneMetadata metadataForRegion = getMetadataForRegionOrCallingCode(countryCode, regionCode);
maybeAppendFormattedExtension(number, metadataForRegion,
PhoneNumberFormat.INTERNATIONAL, formattedNumber);
if (internationalPrefixForFormatting.length() > 0) {
formattedNumber.insert(0, " ").insert(0, countryCode).insert(0, " ")
.insert(0, internationalPrefixForFormatting);
} else {
// Invalid region entered as country-calling-from (so no metadata was found for it) or the
// region chosen has multiple international dialling prefixes.
logger.log(Level.WARNING,
"Trying to format number from invalid region "
+ regionCallingFrom
+ ". International formatting applied.");
prefixNumberWithCountryCallingCode(countryCode,
PhoneNumberFormat.INTERNATIONAL,
formattedNumber);
}
return formattedNumber.toString();
}
/**
* Gets the national significant number of the a phone number. Note a national significant number
* doesn't contain a national prefix or any formatting.
*
* @param number the phone number for which the national significant number is needed
* @return the national significant number of the PhoneNumber object passed in
*/
public String getNationalSignificantNumber(PhoneNumber number) {
// If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
StringBuilder nationalNumber = new StringBuilder();
if (number.isItalianLeadingZero()) {
char[] zeros = new char[number.getNumberOfLeadingZeros()];
Arrays.fill(zeros, '0');
nationalNumber.append(new String(zeros));
}
nationalNumber.append(number.getNationalNumber());
return nationalNumber.toString();
}
/**
* A helper function that is used by format and formatByPattern.
*/
private void prefixNumberWithCountryCallingCode(int countryCallingCode,
PhoneNumberFormat numberFormat,
StringBuilder formattedNumber) {
switch (numberFormat) {
case E164:
formattedNumber.insert(0, countryCallingCode).insert(0, PLUS_SIGN);
return;
case INTERNATIONAL:
formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, PLUS_SIGN);
return;
case RFC3966:
formattedNumber.insert(0, "-").insert(0, countryCallingCode).insert(0, PLUS_SIGN)
.insert(0, RFC3966_PREFIX);
return;
case NATIONAL:
default:
return;
}
}
// Simple wrapper of formatNsn for the common case of no carrier code.
private String formatNsn(String number, PhoneMetadata metadata, PhoneNumberFormat numberFormat) {
return formatNsn(number, metadata, numberFormat, null);
}
// Note in some regions, the national number can be written in two completely different ways
// depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The
// numberFormat parameter here is used to specify which format to use for those cases. If a
// carrierCode is specified, this will be inserted into the formatted string to replace $CC.
private String formatNsn(String number,
PhoneMetadata metadata,
PhoneNumberFormat numberFormat,
String carrierCode) {
List
*
* @param number the number that needs to be checked
* @return a ValidationResult object which indicates whether the number is possible
*/
public ValidationResult isPossibleNumberWithReason(PhoneNumber number) {
String nationalNumber = getNationalSignificantNumber(number);
int countryCode = number.getCountryCode();
// Note: For Russian Fed and NANPA numbers, we just use the rules from the default region (US or
// Russia) since the getRegionCodeForNumber will not work if the number is possible but not
// valid. This would need to be revisited if the possible number pattern ever differed between
// various regions within those plans.
if (!hasValidCountryCallingCode(countryCode)) {
return ValidationResult.INVALID_COUNTRY_CODE;
}
String regionCode = getRegionCodeForCountryCode(countryCode);
// Metadata cannot be null because the country calling code is valid.
PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCode, regionCode);
Pattern possibleNumberPattern =
regexCache.getPatternForRegex(metadata.getGeneralDesc().getPossibleNumberPattern());
return testNumberLengthAgainstPattern(possibleNumberPattern, nationalNumber);
}
/**
* Check whether a phone number is a possible number given a number in the form of a string, and
* the region where the number could be dialed from. It provides a more lenient check than
* {@link #isValidNumber}. See {@link #isPossibleNumber(PhoneNumber)} for details.
*
*
*
* It will throw a NumberParseException if the number starts with a '+' but the country calling
* code supplied after this does not match that of any known region.
*
* @param number non-normalized telephone number that we wish to extract a country calling
* code from - may begin with '+'
* @param defaultRegionMetadata metadata about the region this number may be from
* @param nationalNumber a string buffer to store the national significant number in, in the case
* that a country calling code was extracted. The number is appended to any existing contents.
* If no country calling code was extracted, this will be left unchanged.
* @param keepRawInput true if the country_code_source and preferred_carrier_code fields of
* phoneNumber should be populated.
* @param phoneNumber the PhoneNumber object where the country_code and country_code_source need
* to be populated. Note the country_code is always populated, whereas country_code_source is
* only populated when keepCountryCodeSource is true.
* @return the country calling code extracted or 0 if none could be extracted
*/
// @VisibleForTesting
int maybeExtractCountryCode(String number, PhoneMetadata defaultRegionMetadata,
StringBuilder nationalNumber, boolean keepRawInput,
PhoneNumber phoneNumber)
throws NumberParseException {
if (number.length() == 0) {
return 0;
}
StringBuilder fullNumber = new StringBuilder(number);
// Set the default prefix to be something that will never match.
String possibleCountryIddPrefix = "NonMatch";
if (defaultRegionMetadata != null) {
possibleCountryIddPrefix = defaultRegionMetadata.getInternationalPrefix();
}
CountryCodeSource countryCodeSource =
maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix);
if (keepRawInput) {
phoneNumber.setCountryCodeSource(countryCodeSource);
}
if (countryCodeSource != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
if (fullNumber.length() <= MIN_LENGTH_FOR_NSN) {
throw new NumberParseException(NumberParseException.ErrorType.TOO_SHORT_AFTER_IDD,
"Phone number had an IDD, but after this was not "
+ "long enough to be a viable phone number.");
}
int potentialCountryCode = extractCountryCode(fullNumber, nationalNumber);
if (potentialCountryCode != 0) {
phoneNumber.setCountryCode(potentialCountryCode);
return potentialCountryCode;
}
// If this fails, they must be using a strange country calling code that we don't recognize,
// or that doesn't exist.
throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE,
"Country calling code supplied was not recognised.");
} else if (defaultRegionMetadata != null) {
// Check to see if the number starts with the country calling code for the default region. If
// so, we remove the country calling code, and do some checks on the validity of the number
// before and after.
int defaultCountryCode = defaultRegionMetadata.getCountryCode();
String defaultCountryCodeString = String.valueOf(defaultCountryCode);
String normalizedNumber = fullNumber.toString();
if (normalizedNumber.startsWith(defaultCountryCodeString)) {
StringBuilder potentialNationalNumber =
new StringBuilder(normalizedNumber.substring(defaultCountryCodeString.length()));
PhoneNumberDesc generalDesc = defaultRegionMetadata.getGeneralDesc();
Pattern validNumberPattern =
regexCache.getPatternForRegex(generalDesc.getNationalNumberPattern());
maybeStripNationalPrefixAndCarrierCode(
potentialNationalNumber, defaultRegionMetadata, null /* Don't need the carrier code */);
Pattern possibleNumberPattern =
regexCache.getPatternForRegex(generalDesc.getPossibleNumberPattern());
// If the number was not valid before but is valid now, or if it was too long before, we
// consider the number with the country calling code stripped to be a better result and
// keep that instead.
if ((!validNumberPattern.matcher(fullNumber).matches() &&
validNumberPattern.matcher(potentialNationalNumber).matches()) ||
testNumberLengthAgainstPattern(possibleNumberPattern, fullNumber.toString())
== ValidationResult.TOO_LONG) {
nationalNumber.append(potentialNationalNumber);
if (keepRawInput) {
phoneNumber.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);
}
phoneNumber.setCountryCode(defaultCountryCode);
return defaultCountryCode;
}
}
}
// No country calling code present.
phoneNumber.setCountryCode(0);
return 0;
}
/**
* Strips the IDD from the start of the number if present. Helper function used by
* maybeStripInternationalPrefixAndNormalize.
*/
private boolean parsePrefixAsIdd(Pattern iddPattern, StringBuilder number) {
Matcher m = iddPattern.matcher(number);
if (m.lookingAt()) {
int matchEnd = m.end();
// Only strip this if the first digit after the match is not a 0, since country calling codes
// cannot begin with 0.
Matcher digitMatcher = CAPTURING_DIGIT_PATTERN.matcher(number.substring(matchEnd));
if (digitMatcher.find()) {
String normalizedGroup = normalizeDigitsOnly(digitMatcher.group(1));
if (normalizedGroup.equals("0")) {
return false;
}
}
number.delete(0, matchEnd);
return true;
}
return false;
}
/**
* Strips any international prefix (such as +, 00, 011) present in the number provided, normalizes
* the resulting number, and indicates if an international prefix was present.
*
* @param number the non-normalized telephone number that we wish to strip any international
* dialing prefix from.
* @param possibleIddPrefix the international direct dialing prefix from the region we
* think this number may be dialed in
* @return the corresponding CountryCodeSource if an international dialing prefix could be
* removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did
* not seem to be in international format.
*/
// @VisibleForTesting
CountryCodeSource maybeStripInternationalPrefixAndNormalize(
StringBuilder number,
String possibleIddPrefix) {
if (number.length() == 0) {
return CountryCodeSource.FROM_DEFAULT_COUNTRY;
}
// Check to see if the number begins with one or more plus signs.
Matcher m = PLUS_CHARS_PATTERN.matcher(number);
if (m.lookingAt()) {
number.delete(0, m.end());
// Can now normalize the rest of the number since we've consumed the "+" sign at the start.
normalize(number);
return CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN;
}
// Attempt to parse the first digits as an international prefix.
Pattern iddPattern = regexCache.getPatternForRegex(possibleIddPrefix);
normalize(number);
return parsePrefixAsIdd(iddPattern, number)
? CountryCodeSource.FROM_NUMBER_WITH_IDD
: CountryCodeSource.FROM_DEFAULT_COUNTRY;
}
/**
* Strips any national prefix (such as 0, 1) present in the number provided.
*
* @param number the normalized telephone number that we wish to strip any national
* dialing prefix from
* @param metadata the metadata for the region that we think this number is from
* @param carrierCode a place to insert the carrier code if one is extracted
* @return true if a national prefix or carrier code (or both) could be extracted.
*/
// @VisibleForTesting
boolean maybeStripNationalPrefixAndCarrierCode(
StringBuilder number, PhoneMetadata metadata, StringBuilder carrierCode) {
int numberLength = number.length();
String possibleNationalPrefix = metadata.getNationalPrefixForParsing();
if (numberLength == 0 || possibleNationalPrefix.length() == 0) {
// Early return for numbers of zero length.
return false;
}
// Attempt to parse the first digits as a national prefix.
Matcher prefixMatcher = regexCache.getPatternForRegex(possibleNationalPrefix).matcher(number);
if (prefixMatcher.lookingAt()) {
Pattern nationalNumberRule =
regexCache.getPatternForRegex(metadata.getGeneralDesc().getNationalNumberPattern());
// Check if the original number is viable.
boolean isViableOriginalNumber = nationalNumberRule.matcher(number).matches();
// prefixMatcher.group(numOfGroups) == null implies nothing was captured by the capturing
// groups in possibleNationalPrefix; therefore, no transformation is necessary, and we just
// remove the national prefix.
int numOfGroups = prefixMatcher.groupCount();
String transformRule = metadata.getNationalPrefixTransformRule();
if (transformRule == null || transformRule.length() == 0 ||
prefixMatcher.group(numOfGroups) == null) {
// If the original number was viable, and the resultant number is not, we return.
if (isViableOriginalNumber &&
!nationalNumberRule.matcher(number.substring(prefixMatcher.end())).matches()) {
return false;
}
if (carrierCode != null && numOfGroups > 0 && prefixMatcher.group(numOfGroups) != null) {
carrierCode.append(prefixMatcher.group(1));
}
number.delete(0, prefixMatcher.end());
return true;
} else {
// Check that the resultant number is still viable. If not, return. Check this by copying
// the string buffer and making the transformation on the copy first.
StringBuilder transformedNumber = new StringBuilder(number);
transformedNumber.replace(0, numberLength, prefixMatcher.replaceFirst(transformRule));
if (isViableOriginalNumber &&
!nationalNumberRule.matcher(transformedNumber.toString()).matches()) {
return false;
}
if (carrierCode != null && numOfGroups > 1) {
carrierCode.append(prefixMatcher.group(1));
}
number.replace(0, number.length(), transformedNumber.toString());
return true;
}
}
return false;
}
/**
* Strips any extension (as in, the part of the number dialled after the call is connected,
* usually indicated with extn, ext, x or similar) from the end of the number, and returns it.
*
* @param number the non-normalized telephone number that we wish to strip the extension from
* @return the phone extension
*/
// @VisibleForTesting
String maybeStripExtension(StringBuilder number) {
Matcher m = EXTN_PATTERN.matcher(number);
// If we find a potential extension, and the number preceding this is a viable number, we assume
// it is an extension.
if (m.find() && isViablePhoneNumber(number.substring(0, m.start()))) {
// The numbers are captured into groups in the regular expression.
for (int i = 1, length = m.groupCount(); i <= length; i++) {
if (m.group(i) != null) {
// We go through the capturing groups until we find one that captured some digits. If none
// did, then we will return the empty string.
String extension = m.group(i);
number.delete(m.start(), number.length());
return extension;
}
}
}
return "";
}
/**
* Checks to see that the region code used is valid, or if it is not valid, that the number to
* parse starts with a + symbol so that we can attempt to infer the region from the number.
* Returns false if it cannot use the region provided and the region cannot be inferred.
*/
private boolean checkRegionForParsing(String numberToParse, String defaultRegion) {
if (!isValidRegionCode(defaultRegion)) {
// If the number is null or empty, we can't infer the region.
if ((numberToParse == null) || (numberToParse.length() == 0) ||
!PLUS_CHARS_PATTERN.matcher(numberToParse).lookingAt()) {
return false;
}
}
return true;
}
/**
* Parses a string and returns it in proto buffer format. This method will throw a
* {@link com.google.i18n.phonenumbers.NumberParseException} if the number is not considered to be
* a possible number. Note that validation of whether the number is actually a valid number for a
* particular region is not performed. This can be done separately with {@link #isValidNumber}.
*
* @param numberToParse number that we are attempting to parse. This can contain formatting
* such as +, ( and -, as well as a phone number extension. It can also
* be provided in RFC3966 format.
* @param defaultRegion region that we are expecting the number to be from. This is only used
* if the number being parsed is not written in international format.
* The country_code for the number in this case would be stored as that
* of the default region supplied. If the number is guaranteed to
* start with a '+' followed by the country calling code, then
* "ZZ" or null can be supplied.
* @return a phone number proto buffer filled with the parsed number
* @throws NumberParseException if the string is not considered to be a viable phone number or if
* no default region was supplied and the number is not in
* international format (does not start with +)
*/
public PhoneNumber parse(String numberToParse, String defaultRegion)
throws NumberParseException {
PhoneNumber phoneNumber = new PhoneNumber();
parse(numberToParse, defaultRegion, phoneNumber);
return phoneNumber;
}
/**
* Same as {@link #parse(String, String)}, but accepts mutable PhoneNumber as a parameter to
* decrease object creation when invoked many times.
*/
public void parse(String numberToParse, String defaultRegion, PhoneNumber phoneNumber)
throws NumberParseException {
parseHelper(numberToParse, defaultRegion, false, true, phoneNumber);
}
/**
* Parses a string and returns it in proto buffer format. This method differs from {@link #parse}
* in that it always populates the raw_input field of the protocol buffer with numberToParse as
* well as the country_code_source field.
*
* @param numberToParse number that we are attempting to parse. This can contain formatting
* such as +, ( and -, as well as a phone number extension.
* @param defaultRegion region that we are expecting the number to be from. This is only used
* if the number being parsed is not written in international format.
* The country calling code for the number in this case would be stored
* as that of the default region supplied.
* @return a phone number proto buffer filled with the parsed number
* @throws NumberParseException if the string is not considered to be a viable phone number or if
* no default region was supplied
*/
public PhoneNumber parseAndKeepRawInput(String numberToParse, String defaultRegion)
throws NumberParseException {
PhoneNumber phoneNumber = new PhoneNumber();
parseAndKeepRawInput(numberToParse, defaultRegion, phoneNumber);
return phoneNumber;
}
/**
* Same as{@link #parseAndKeepRawInput(String, String)}, but accepts a mutable PhoneNumber as
* a parameter to decrease object creation when invoked many times.
*/
public void parseAndKeepRawInput(String numberToParse, String defaultRegion,
PhoneNumber phoneNumber)
throws NumberParseException {
parseHelper(numberToParse, defaultRegion, true, true, phoneNumber);
}
/**
* Returns an iterable over all {@link PhoneNumberMatch PhoneNumberMatches} in {@code text}. This
* is a shortcut for {@link #findNumbers(CharSequence, String, Leniency, long)
* getMatcher(text, defaultRegion, Leniency.VALID, Long.MAX_VALUE)}.
*
* @param text the text to search for phone numbers, null for no text
* @param defaultRegion region that we are expecting the number to be from. This is only used
* if the number being parsed is not written in international format. The
* country_code for the number in this case would be stored as that of
* the default region supplied. May be null if only international
* numbers are expected.
*/
public Iterable