/** * Gets the expected cost category of a short number (however, nothing is implied about its * validity). If it is important that the number is valid, then its validity must first be checked * using {@link isValidShortNumber}. Note that emergency numbers are always considered toll-free. * Example usage: * <pre>{@code * PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance(); * PhoneNumber number = phoneUtil.parse("110", "FR"); * if (shortInfo.isValidShortNumber(number)) { * ShortNumberInfo.ShortNumberCost cost = shortInfo.getExpectedCost(number); * // Do something with the cost information here. * }}</pre> * * @param number the short number for which we want to know the expected cost category * @return the expected cost category of the short number. Returns UNKNOWN_COST if the number does * not match a cost category. Note that an invalid number may match any cost category. */ public ShortNumberCost getExpectedCost(PhoneNumber number) { List <String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode()); String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); // Note that regionCode may be null, in which case phoneMetadata will also be null. PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); if (phoneMetadata == null) { return(ShortNumberCost.UNKNOWN_COST); } String nationalNumber = phoneUtil.getNationalSignificantNumber(number); // The cost categories are tested in order of decreasing expense, since if for some reason the // patterns overlap the most expensive matching cost category should be returned. if (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getPremiumRate())) { return(ShortNumberCost.PREMIUM_RATE); } if (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getStandardRate())) { return(ShortNumberCost.STANDARD_RATE); } if (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getTollFree())) { return(ShortNumberCost.TOLL_FREE); } if (isEmergencyNumber(nationalNumber, regionCode)) { // Emergency numbers are implicitly toll-free. return(ShortNumberCost.TOLL_FREE); } return(ShortNumberCost.UNKNOWN_COST); }
/** * Extracts the country calling code from the beginning of nationalNumber to * prefixBeforeNationalNumber when they are available, and places the remaining input into * nationalNumber. * * @return true when a valid country calling code can be found. */ private boolean attemptToExtractCountryCallingCode() { if (nationalNumber.length() == 0) { return(false); } StringBuilder numberWithoutCountryCallingCode = new StringBuilder(); int countryCode = phoneUtil.extractCountryCode(nationalNumber, numberWithoutCountryCallingCode); if (countryCode == 0) { return(false); } nationalNumber.setLength(0); nationalNumber.append(numberWithoutCountryCallingCode); String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode); if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(newRegionCode)) { currentMetadata = phoneUtil.getMetadataForNonGeographicalRegion(countryCode); } else if (!newRegionCode.equals(defaultCountry)) { currentMetadata = getMetadataForRegion(newRegionCode); } String countryCodeString = Integer.toString(countryCode); prefixBeforeNationalNumber.append(countryCodeString).append(SEPARATOR_BEFORE_NATIONAL_NUMBER); return(true); }
private boolean matchesEmergencyNumberHelper(String number, String regionCode, boolean allowPrefixMatch) { number = PhoneNumberUtil.extractPossibleNumber(number); if (PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(number).lookingAt()) { // Returns false if the number starts with a plus sign. We don't believe dialing the country // code before emergency numbers (e.g. +1911) works, but later, if that proves to work, we can // add additional logic here to handle it. return(false); } PhoneMetadata metadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); if (metadata == null || !metadata.hasEmergency()) { return(false); } Pattern emergencyNumberPattern = Pattern.compile(metadata.getEmergency().getNationalNumberPattern()); String normalizedNumber = PhoneNumberUtil.normalizeDigitsOnly(number); // In Brazil and Chile, emergency numbers don't work when additional digits are appended. return((!allowPrefixMatch || regionCode == "BR" || regionCode == "CL") ? emergencyNumberPattern.matcher(normalizedNumber).matches() : emergencyNumberPattern.matcher(normalizedNumber).lookingAt()); }
internal static boolean checkNumberGroupingIsValid( PhoneNumber number, String candidate, PhoneNumberUtil util, NumberGroupingChecker checker) { // TODO: Evaluate how this works for other locales (testing has been limited to NANPA regions) // and optimise if necessary. StringBuilder normalizedCandidate = PhoneNumberUtil.normalizeDigits(candidate, true /* keep non-digits */); String[] formattedNumberGroups = getNationalNumberGroups(util, number, null); if (checker.checkGroups(util, number, normalizedCandidate, formattedNumberGroups)) { return(true); } // If this didn't pass, see if there are any alternate formats, and try them instead. PhoneMetadata alternateFormats = MetadataManager.getAlternateFormatsForCountry(number.getCountryCode()); if (alternateFormats != null) { foreach (NumberFormat alternateFormat in alternateFormats.numberFormats()) { formattedNumberGroups = getNationalNumberGroups(util, number, alternateFormat); if (checker.checkGroups(util, number, normalizedCandidate, formattedNumberGroups)) { return(true); } } } return(false); }
/** * Gets a valid short number for the specified cost category. * * @param regionCode the region for which an example short number is needed * @param cost the cost category of number that is needed * @return a valid short number for the specified region and cost category. Returns an empty * string when the metadata does not contain such information, or the cost is UNKNOWN_COST. */ // @VisibleForTesting internal String getExampleShortNumberForCost(String regionCode, ShortNumberCost cost) { PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); if (phoneMetadata == null) { return(""); } PhoneNumberDesc desc = null; switch (cost) { case ShortNumberCost.TOLL_FREE: desc = phoneMetadata.getTollFree(); break; case ShortNumberCost.STANDARD_RATE: desc = phoneMetadata.getStandardRate(); break; case ShortNumberCost.PREMIUM_RATE: desc = phoneMetadata.getPremiumRate(); break; default: // UNKNOWN_COST numbers are computed by the process of elimination from the other cost // categories. break; } if (desc != null && desc.hasExampleNumber()) { return(desc.getExampleNumber()); } return(""); }
// Helper method to get the region code for a given phone number, from a list of possible region // codes. If the list contains more than one region, the first region for which the number is // valid is returned. private String getRegionCodeForShortNumberFromRegionList(PhoneNumber number, List <String> regionCodes) { if (regionCodes.size() == 0) { return(null); } else if (regionCodes.size() == 1) { return(regionCodes.get(0)); } String nationalNumber = phoneUtil.getNationalSignificantNumber(number); foreach (String regionCode in regionCodes) { PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); if (phoneMetadata != null && phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getShortCode())) { // The number is valid for this region. return(regionCode); } } return(null); }
/** * Tests whether a short number matches a valid pattern. Note that this doesn't verify the number * is actually in use, which is impossible to tell by just looking at the number itself. * * @param shortNumber the short number to check as a string * @param regionDialingFrom the region from which the number is dialed * @return whether the short number matches a valid pattern */ public boolean isValidShortNumber(String shortNumber, String regionDialingFrom) { PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom); if (phoneMetadata == null) { return(false); } PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc(); if (!generalDesc.hasNationalNumberPattern() || !phoneUtil.isNumberMatchingDesc(shortNumber, generalDesc)) { return(false); } PhoneNumberDesc shortNumberDesc = phoneMetadata.getShortCode(); if (!shortNumberDesc.hasNationalNumberPattern()) { logger.log(Level.WARNING, "No short code national number pattern found for region: " + regionDialingFrom); return(false); } return(phoneUtil.isNumberMatchingDesc(shortNumber, shortNumberDesc)); }
[TestMethod] public void testShortNumberMetadataContainsData() { // We should have some data for France. PhoneMetadata franceShortNumberMetadata = MetadataManager.getShortNumberMetadataForRegion("FR"); assertNotNull(franceShortNumberMetadata); assertTrue(franceShortNumberMetadata.hasShortCode()); }
[TestMethod] public void testAlternateFormatsContainsData() { // We should have some data for Germany. PhoneMetadata germanyAlternateFormats = MetadataManager.getAlternateFormatsForCountry(49); assertNotNull(germanyAlternateFormats); assertTrue(germanyAlternateFormats.numberFormats().size() > 0); }
/** * Given a valid short number, determines whether it is carrier-specific (however, nothing is * implied about its validity). If it is important that the number is valid, then its validity * must first be checked using {@link isValidShortNumber}. * * @param number the valid short number to check * @return whether the short number is carrier-specific (assuming the input was a valid short * number). */ public boolean isCarrierSpecific(PhoneNumber number) { List <String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode()); String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); String nationalNumber = phoneUtil.getNationalSignificantNumber(number); PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); return((phoneMetadata != null) && (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getCarrierSpecific()))); }
/** * Check whether a short number is a possible number, given the number in the form of a string, * and the region where the number is dialed from. This provides a more lenient check than * {@link #isValidShortNumber}. * * @param shortNumber the short number to check as a string * @param regionDialingFrom the region from which the number is dialed * @return whether the number is a possible short number */ public boolean isPossibleShortNumber(String shortNumber, String regionDialingFrom) { PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom); if (phoneMetadata == null) { return(false); } PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc(); return(phoneUtil.isNumberPossibleForDesc(shortNumber, generalDesc)); }
// The metadata needed by this class is the same for all regions sharing the same country calling // code. Therefore, we return the metadata for "main" region for this country calling code. private PhoneMetadata getMetadataForRegion(String regionCode) { int countryCallingCode = phoneUtil.getCountryCodeForRegion(regionCode); String mainCountry = phoneUtil.getRegionCodeForCountryCode(countryCallingCode); PhoneMetadata metadata = phoneUtil.getMetadataForRegion(mainCountry); if (metadata != null) { return(metadata); } // Set to a default instance of the metadata. This allows us to function with an incorrect // region code, even if formatting only works for numbers specified with "+". return(EMPTY_METADATA); }
/** * Gets a valid short number for the specified region. * * @param regionCode the region for which an example short number is needed * @return a valid short number for the specified region. Returns an empty string when the * metadata does not contain such information. */ // @VisibleForTesting internal String getExampleShortNumber(String regionCode) { PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); if (phoneMetadata == null) { return(""); } PhoneNumberDesc desc = phoneMetadata.getShortCode(); if (desc.hasExampleNumber()) { return(desc.getExampleNumber()); } return(""); }
internal static boolean isNationalPrefixPresentIfRequired(PhoneNumber number, PhoneNumberUtil util) { // First, check how we deduced the country code. If it was written in international format, then // the national prefix is not required. if (number.getCountryCodeSource() != CountryCodeSource.FROM_DEFAULT_COUNTRY) { return(true); } String phoneNumberRegion = util.getRegionCodeForCountryCode(number.getCountryCode()); PhoneMetadata metadata = util.getMetadataForRegion(phoneNumberRegion); if (metadata == null) { return(true); } // Check if a national prefix should be present when formatting this number. String nationalNumber = util.getNationalSignificantNumber(number); NumberFormat formatRule = util.chooseFormattingPatternForNumber(metadata.numberFormats(), nationalNumber); // To do this, we check that a national prefix formatting rule was present and that it wasn't // just the first-group symbol ($1) with punctuation. if ((formatRule != null) && formatRule.getNationalPrefixFormattingRule().length() > 0) { if (formatRule.isNationalPrefixOptionalWhenFormatting()) { // The national-prefix is optional in these cases, so we don't need to check if it was // present. return(true); } if (PhoneNumberUtil.formattingRuleHasFirstGroupOnly( formatRule.getNationalPrefixFormattingRule())) { // National Prefix not needed for this number. return(true); } // Normalize the remainder. String rawInputCopy = PhoneNumberUtil.normalizeDigitsOnly(number.getRawInput()); StringBuilder rawInput = new StringBuilder(rawInputCopy); // Check if we found a national prefix and/or carrier code at the start of the raw input, and // return the result. return(util.maybeStripNationalPrefixAndCarrierCode(rawInput, metadata, null)); } return(true); }
/** * Clears the internal state of the formatter, so it can be reused. */ public void clear() { currentOutput = ""; accruedInput.setLength(0); accruedInputWithoutFormatting.setLength(0); formattingTemplate.setLength(0); lastMatchPosition = 0; currentFormattingPattern = ""; prefixBeforeNationalNumber.setLength(0); nationalPrefixExtracted = ""; nationalNumber.setLength(0); ableToFormat = true; inputHasFormatting = false; positionToRemember = 0; originalPosition = 0; isCompleteNumber = false; isExpectingCountryCallingCode = false; possibleFormats.clear(); shouldAddSpaceAfterNationalPrefix = false; if (!currentMetadata.equals(defaultMetadata)) { currentMetadata = getMetadataForRegion(defaultCountry); } }
[TestMethod] public void testShortNumberMetadataFailsGracefully() { PhoneMetadata noShortNumberMetadata = MetadataManager.getShortNumberMetadataForRegion("XXX"); assertNull(noShortNumberMetadata); }
[TestMethod] public void testAlternateFormatsFailsGracefully() { PhoneMetadata noAlternateFormats = MetadataManager.getAlternateFormatsForCountry(999); assertNull(noAlternateFormats); }
public void testMaybeStripNationalPrefix() { PhoneMetadata metadata = new PhoneMetadata(); metadata.setNationalPrefixForParsing("34"); metadata.setGeneralDesc(new PhoneNumberDesc().setNationalNumberPattern("\\d{4,8}")); StringBuilder numberToStrip = new StringBuilder("34356778"); String strippedNumber = "356778"; assertTrue(phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); assertEquals("Should have had national prefix stripped.", strippedNumber, numberToStrip.toString()); // Retry stripping - now the number should not start with the national prefix, so no more // stripping should occur. assertFalse(phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); assertEquals("Should have had no change - no national prefix present.", strippedNumber, numberToStrip.toString()); // Some countries have no national prefix. Repeat test with none specified. metadata.setNationalPrefixForParsing(""); assertFalse(phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); assertEquals("Should not strip anything with empty national prefix.", strippedNumber, numberToStrip.toString()); // If the resultant number doesn't match the national rule, it shouldn't be stripped. metadata.setNationalPrefixForParsing("3"); numberToStrip = new StringBuilder("3123"); strippedNumber = "3123"; assertFalse(phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); assertEquals("Should have had no change - after stripping, it wouldn't have matched " + "the national rule.", strippedNumber, numberToStrip.toString()); // Test extracting carrier selection code. metadata.setNationalPrefixForParsing("0(81)?"); numberToStrip = new StringBuilder("08122123456"); strippedNumber = "22123456"; StringBuilder carrierCode = new StringBuilder(); assertTrue(phoneUtil.maybeStripNationalPrefixAndCarrierCode( numberToStrip, metadata, carrierCode)); assertEquals("81", carrierCode.toString()); assertEquals("Should have had national prefix and carrier code stripped.", strippedNumber, numberToStrip.toString()); // If there was a transform rule, check it was applied. metadata.setNationalPrefixTransformRule("5$15"); // Note that a capturing group is present here. metadata.setNationalPrefixForParsing("0(\\d{2})"); numberToStrip = new StringBuilder("031123"); String transformedNumber = "5315123"; assertTrue(phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata, null)); assertEquals("Should transform the 031 to a 5315.", transformedNumber, numberToStrip.toString()); }
/** * Constructs an as-you-type formatter. Should be obtained from {@link * PhoneNumberUtil#getAsYouTypeFormatter}. * * @param regionCode the country/region where the phone number is being entered */ internal AsYouTypeFormatter(String regionCode) { defaultCountry = regionCode; currentMetadata = getMetadataForRegion(defaultCountry); defaultMetadata = currentMetadata; }
/** * Constructs an as-you-type formatter. Should be obtained from {@link * PhoneNumberUtil#getAsYouTypeFormatter}. * * @param regionCode the country/region where the phone number is being entered */ internal AsYouTypeFormatter(String regionCode) { defaultCountry = regionCode; currentMetadata = getMetadataForRegion(defaultCountry); defaultMetadata = currentMetadata; }
/** * Extracts the country calling code from the beginning of nationalNumber to * prefixBeforeNationalNumber when they are available, and places the remaining input into * nationalNumber. * * @return true when a valid country calling code can be found. */ private boolean attemptToExtractCountryCallingCode() { if (nationalNumber.length() == 0) { return false; } StringBuilder numberWithoutCountryCallingCode = new StringBuilder(); int countryCode = phoneUtil.extractCountryCode(nationalNumber, numberWithoutCountryCallingCode); if (countryCode == 0) { return false; } nationalNumber.setLength(0); nationalNumber.append(numberWithoutCountryCallingCode); String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode); if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(newRegionCode)) { currentMetadata = phoneUtil.getMetadataForNonGeographicalRegion(countryCode); } else if (!newRegionCode.equals(defaultCountry)) { currentMetadata = getMetadataForRegion(newRegionCode); } String countryCodeString = Integer.toString(countryCode); prefixBeforeNationalNumber.append(countryCodeString).append(SEPARATOR_BEFORE_NATIONAL_NUMBER); return true; }
/** * Clears the internal state of the formatter, so it can be reused. */ public void clear() { currentOutput = ""; accruedInput.setLength(0); accruedInputWithoutFormatting.setLength(0); formattingTemplate.setLength(0); lastMatchPosition = 0; currentFormattingPattern = ""; prefixBeforeNationalNumber.setLength(0); nationalPrefixExtracted = ""; nationalNumber.setLength(0); ableToFormat = true; inputHasFormatting = false; positionToRemember = 0; originalPosition = 0; isCompleteNumber = false; isExpectingCountryCallingCode = false; possibleFormats.clear(); shouldAddSpaceAfterNationalPrefix = false; if (!currentMetadata.equals(defaultMetadata)) { currentMetadata = getMetadataForRegion(defaultCountry); } }