Browse Source

CPP/JAVA: Introducing new public APIs in ShortNumberInfo/short_number_info. Deprecating old methods. Also includes a refactoring change introducing a matcher API.

pull/567/head
Lara Scheidegger 11 years ago
committed by Mihaela Rosca
parent
commit
ada3e9e73f
20 changed files with 1464 additions and 865 deletions
  1. +3
    -0
      cpp/CMakeLists.txt
  2. +38
    -0
      cpp/src/phonenumbers/matcher_api.h
  3. +52
    -0
      cpp/src/phonenumbers/regex_based_matcher.cc
  4. +45
    -0
      cpp/src/phonenumbers/regex_based_matcher.h
  5. +122
    -56
      cpp/src/phonenumbers/shortnumberinfo.cc
  6. +57
    -1
      cpp/src/phonenumbers/shortnumberinfo.h
  7. +629
    -610
      cpp/src/phonenumbers/test_metadata.cc
  8. +131
    -76
      cpp/test/phonenumbers/shortnumberinfo_test.cc
  9. +12
    -0
      cpp/test/phonenumbers/test_util.h
  10. +223
    -55
      java/libphonenumber/src/com/google/i18n/phonenumbers/ShortNumberInfo.java
  11. +22
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/internal/MatcherApi.java
  12. +36
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/internal/RegexBasedMatcher.java
  13. +3
    -1
      java/libphonenumber/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java
  14. +12
    -9
      java/libphonenumber/test/com/google/i18n/phonenumbers/ExampleNumbersTest.java
  15. +68
    -55
      java/libphonenumber/test/com/google/i18n/phonenumbers/ShortNumberInfoTest.java
  16. BIN
      java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_BB
  17. BIN
      java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_CA
  18. BIN
      java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_CX
  19. BIN
      java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_FR
  20. +11
    -2
      resources/PhoneNumberMetadataForTesting.xml

+ 3
- 0
cpp/CMakeLists.txt View File

@ -192,6 +192,7 @@ set (
"src/phonenumbers/phonenumber.cc"
"src/phonenumbers/phonenumber.pb.cc" # Generated by Protocol Buffers.
"src/phonenumbers/phonenumberutil.cc"
"src/phonenumbers/regex_based_matcher.cc"
"src/phonenumbers/regexp_cache.cc"
"src/phonenumbers/shortnumberinfo.cc"
"src/phonenumbers/string_byte_sink.cc"
@ -448,6 +449,7 @@ set (TEST_SOURCES
"test/phonenumbers/regexp_adapter_test.cc"
"test/phonenumbers/regexp_cache_test.cc"
"test/phonenumbers/run_tests.cc"
"test/phonenumbers/shortnumberinfo_test.cc"
"test/phonenumbers/stringutil_test.cc"
"test/phonenumbers/test_util.cc"
"test/phonenumbers/unicodestring_test.cc"
@ -502,6 +504,7 @@ install (FILES
"src/phonenumbers/asyoutypeformatter.h"
"src/phonenumbers/callback.h"
"src/phonenumbers/logger.h"
"src/phonenumbers/matcher_api.h"
"src/phonenumbers/phonenumber.pb.h"
"src/phonenumbers/phonemetadata.pb.h"
"src/phonenumbers/phonenumberutil.h"


+ 38
- 0
cpp/src/phonenumbers/matcher_api.h View File

@ -0,0 +1,38 @@
#ifndef I18N_PHONENUMBERS_MATCHER_API_H_
#define I18N_PHONENUMBERS_MATCHER_API_H_
#include <string>
namespace i18n {
namespace phonenumbers {
using std::string;
class PhoneNumberDesc;
// Internal phonenumber matching API used to isolate the underlying
// implementation of the matcher and allow different implementations to be
// swapped in easily.
class MatcherApi {
public:
virtual ~MatcherApi() {}
// Returns whether the given national number (a string containing only decimal
// digits) matches the national number pattern defined in the given
// PhoneNumberDesc message.
virtual bool MatchesNationalNumber(const string& national_number,
const PhoneNumberDesc& number_desc,
bool allow_prefix_match) const = 0;
// Returns whether the given national number (a string containing only decimal
// digits) matches the possible number pattern defined in the given
// PhoneNumberDesc message.
virtual bool MatchesPossibleNumber(
const string& national_number,
const PhoneNumberDesc& number_desc) const = 0;
};
} // namespace phonenumbers
} // namespace i18n
#endif // I18N_PHONENUMBERS_MATCHER_API_H_

+ 52
- 0
cpp/src/phonenumbers/regex_based_matcher.cc View File

@ -0,0 +1,52 @@
#include "phonenumbers/regex_based_matcher.h"
#include <memory>
#include <string>
#include "phonenumbers/base/memory/scoped_ptr.h"
#include "phonenumbers/phonemetadata.pb.h"
#include "phonenumbers/regexp_adapter.h"
#include "phonenumbers/regexp_cache.h"
#include "phonenumbers/regexp_factory.h"
namespace i18n {
namespace phonenumbers {
using std::string;
// Same implementations of AbstractRegExpFactory and RegExpCache in
// PhoneNumberUtil (copy from phonenumberutil.cc).
RegexBasedMatcher::RegexBasedMatcher()
: regexp_factory_(new RegExpFactory()),
regexp_cache_(new RegExpCache(*regexp_factory_, 128)) {}
RegexBasedMatcher::~RegexBasedMatcher() {}
bool RegexBasedMatcher::MatchesNationalNumber(
const string& national_number, const PhoneNumberDesc& number_desc,
bool allow_prefix_match) const {
return Match(national_number, number_desc.national_number_pattern(),
allow_prefix_match);
}
bool RegexBasedMatcher::MatchesPossibleNumber(
const string& national_number, const PhoneNumberDesc& number_desc) const {
return Match(national_number, number_desc.possible_number_pattern(), false);
}
bool RegexBasedMatcher::Match(const string& national_number,
const string& number_pattern,
bool allow_prefix_match) const {
const RegExp& regexp(regexp_cache_->GetRegExp(number_pattern));
if (allow_prefix_match) {
const scoped_ptr<RegExpInput> normalized_number_input(
regexp_factory_->CreateInput(national_number));
return regexp.Consume(normalized_number_input.get());
} else {
return regexp.FullMatch(national_number);
}
}
} // namespace phonenumbers
} // namespace i18n

+ 45
- 0
cpp/src/phonenumbers/regex_based_matcher.h View File

@ -0,0 +1,45 @@
#ifndef I18N_PHONENUMBERS_REGEX_BASED_MATCHER_H_
#define I18N_PHONENUMBERS_REGEX_BASED_MATCHER_H_
#include <memory>
#include <string>
#include "phonenumbers/base/basictypes.h"
#include "phonenumbers/base/memory/scoped_ptr.h"
#include "phonenumbers/matcher_api.h"
namespace i18n {
namespace phonenumbers {
class AbstractRegExpFactory;
class PhoneNumberDesc;
class RegExpCache;
// Implementation of the matcher API using the regular expressions in the
// PhoneNumberDesc proto message to match numbers.
class RegexBasedMatcher : public MatcherApi {
public:
RegexBasedMatcher();
~RegexBasedMatcher();
bool MatchesNationalNumber(const string& national_number,
const PhoneNumberDesc& number_desc,
bool allow_prefix_match) const;
bool MatchesPossibleNumber(const string& national_number,
const PhoneNumberDesc& number_desc) const;
private:
bool Match(const string& national_number, const string& number_pattern,
bool allow_prefix_match) const;
const scoped_ptr<const AbstractRegExpFactory> regexp_factory_;
const scoped_ptr<RegExpCache> regexp_cache_;
DISALLOW_COPY_AND_ASSIGN(RegexBasedMatcher);
};
} // namespace phonenumbers
} // namespace i18n
#endif // I18N_PHONENUMBERS_REGEX_BASED_MATCHER_H_

+ 122
- 56
cpp/src/phonenumbers/shortnumberinfo.cc View File

@ -22,10 +22,10 @@
#include "phonenumbers/base/memory/scoped_ptr.h"
#include "phonenumbers/default_logger.h"
#include "phonenumbers/matcher_api.h"
#include "phonenumbers/phonemetadata.pb.h"
#include "phonenumbers/phonenumberutil.h"
#include "phonenumbers/regexp_adapter.h"
#include "phonenumbers/regexp_factory.h"
#include "phonenumbers/regex_based_matcher.h"
#include "phonenumbers/region_code.h"
#include "phonenumbers/short_metadata.h"
@ -46,6 +46,7 @@ bool LoadCompiledInMetadata(PhoneMetadataCollection* metadata) {
ShortNumberInfo::ShortNumberInfo()
: phone_util_(*PhoneNumberUtil::GetInstance()),
matcher_api_(new RegexBasedMatcher()),
region_to_short_metadata_map_(new map<string, PhoneMetadata>()),
regions_where_emergency_numbers_must_be_exact_(new set<string>()) {
PhoneMetadataCollection metadata_collection;
@ -65,6 +66,8 @@ ShortNumberInfo::ShortNumberInfo()
regions_where_emergency_numbers_must_be_exact_->insert("NI");
}
ShortNumberInfo::~ShortNumberInfo() {}
// Returns a pointer to the phone metadata for the appropriate region or NULL
// if the region code is invalid or unknown.
const PhoneMetadata* ShortNumberInfo::GetMetadataForRegion(
@ -77,74 +80,113 @@ const PhoneMetadata* ShortNumberInfo::GetMetadataForRegion(
return NULL;
}
bool ShortNumberInfo::IsPossibleShortNumberForRegion(const string& short_number,
namespace {
// Same as the matchesPossibleNumberAndNationalNumber method in
// java/libphonenumber/src/com/google/i18n/phonenumbers/ShortNumberInfo.java
// TODO: Once we have benchmarked ShortNumberInfo, consider if it is
// worth keeping this performance optimization, and if so move this into the
// matcher implementation.
bool MatchesPossibleNumberAndNationalNumber(
const MatcherApi& matcher_api, const string& number,
const PhoneNumberDesc& number_desc) {
return matcher_api.MatchesPossibleNumber(number, number_desc) &&
matcher_api.MatchesNationalNumber(number, number_desc, false);
}
} // namespace
bool ShortNumberInfo::IsPossibleShortNumberForRegion(
const string& short_number, const string& region_dialing_from) const {
const PhoneMetadata* phone_metadata =
GetMetadataForRegion(region_dialing_from);
if (!phone_metadata) {
return false;
}
const PhoneNumberDesc& general_desc = phone_metadata->general_desc();
return matcher_api_->MatchesPossibleNumber(short_number, general_desc);
}
bool ShortNumberInfo::IsPossibleShortNumberForRegion(const PhoneNumber& number,
const string& region_dialing_from) const {
const PhoneMetadata* phone_metadata =
GetMetadataForRegion(region_dialing_from);
if (!phone_metadata) {
return false;
}
string short_number;
phone_util_.GetNationalSignificantNumber(number, &short_number);
const PhoneNumberDesc& general_desc = phone_metadata->general_desc();
return phone_util_.IsNumberPossibleForDesc(short_number, general_desc);
return matcher_api_->MatchesPossibleNumber(short_number, general_desc);
}
bool ShortNumberInfo::IsPossibleShortNumber(const PhoneNumber& number) const {
list<string> region_codes;
phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
&region_codes);
&region_codes);
string short_number;
phone_util_.GetNationalSignificantNumber(number, &short_number);
for (list<string>::const_iterator it = region_codes.begin();
it != region_codes.end(); ++it) {
const PhoneMetadata* phone_metadata = GetMetadataForRegion(*it);
if (phone_util_.IsNumberPossibleForDesc(short_number,
phone_metadata->general_desc())) {
if (matcher_api_->MatchesPossibleNumber(short_number,
phone_metadata->general_desc())) {
return true;
}
}
return false;
}
bool ShortNumberInfo::IsValidShortNumberForRegion(const string& short_number,
const string& region_dialing_from) const {
bool ShortNumberInfo::IsValidShortNumberForRegion(
const string& short_number, const string& region_dialing_from) const {
const PhoneMetadata* phone_metadata =
GetMetadataForRegion(region_dialing_from);
if (!phone_metadata) {
return false;
}
const PhoneNumberDesc& general_desc = phone_metadata->general_desc();
if (!general_desc.has_national_number_pattern() ||
!phone_util_.IsNumberMatchingDesc(short_number, general_desc)) {
if (!MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
general_desc)) {
return false;
}
const PhoneNumberDesc& short_number_desc = phone_metadata->short_code();
if (!short_number_desc.has_national_number_pattern()) {
LOG(WARNING) << "No short code national number pattern found for region: "
<< region_dialing_from;
return MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
short_number_desc);
}
bool ShortNumberInfo::IsValidShortNumberForRegion(
const PhoneNumber& number, const string& region_dialing_from) const {
const PhoneMetadata* phone_metadata =
GetMetadataForRegion(region_dialing_from);
if (!phone_metadata) {
return false;
}
return phone_util_.IsNumberMatchingDesc(short_number, short_number_desc);
string short_number;
phone_util_.GetNationalSignificantNumber(number, &short_number);
const PhoneNumberDesc& general_desc = phone_metadata->general_desc();
if (!MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
general_desc)) {
return false;
}
const PhoneNumberDesc& short_number_desc = phone_metadata->short_code();
return MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
short_number_desc);
}
bool ShortNumberInfo::IsValidShortNumber(const PhoneNumber& number) const {
list<string> region_codes;
phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
&region_codes);
string short_number;
phone_util_.GetNationalSignificantNumber(number, &short_number);
&region_codes);
string region_code;
GetRegionCodeForShortNumberFromRegionList(number,
region_codes, &region_code);
GetRegionCodeForShortNumberFromRegionList(number, region_codes, &region_code);
if (region_codes.size() > 1 && region_code != RegionCode::GetUnknown()) {
return true;
}
return IsValidShortNumberForRegion(short_number, region_code);
return IsValidShortNumberForRegion(number, region_code);
}
ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCostForRegion(
const string& short_number, const string& region_dialing_from) const {
const PhoneMetadata* phone_metadata = GetMetadataForRegion(
region_dialing_from);
const PhoneMetadata* phone_metadata =
GetMetadataForRegion(region_dialing_from);
if (!phone_metadata) {
return ShortNumberInfo::UNKNOWN_COST;
}
@ -152,16 +194,48 @@ ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCostForRegion(
// 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 (phone_util_.IsNumberMatchingDesc(short_number,
phone_metadata->premium_rate())) {
if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
phone_metadata->premium_rate())) {
return ShortNumberInfo::PREMIUM_RATE;
}
if (phone_util_.IsNumberMatchingDesc(short_number,
phone_metadata->standard_rate())) {
if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
phone_metadata->standard_rate())) {
return ShortNumberInfo::STANDARD_RATE;
}
if (phone_util_.IsNumberMatchingDesc(short_number,
phone_metadata->toll_free())) {
if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
phone_metadata->toll_free())) {
return ShortNumberInfo::TOLL_FREE;
}
if (IsEmergencyNumber(short_number, region_dialing_from)) {
// Emergency numbers are implicitly toll-free.
return ShortNumberInfo::TOLL_FREE;
}
return ShortNumberInfo::UNKNOWN_COST;
}
ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCostForRegion(
const PhoneNumber& number, const string& region_dialing_from) const {
const PhoneMetadata* phone_metadata =
GetMetadataForRegion(region_dialing_from);
if (!phone_metadata) {
return ShortNumberInfo::UNKNOWN_COST;
}
string short_number;
phone_util_.GetNationalSignificantNumber(number, &short_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 (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
phone_metadata->premium_rate())) {
return ShortNumberInfo::PREMIUM_RATE;
}
if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
phone_metadata->standard_rate())) {
return ShortNumberInfo::STANDARD_RATE;
}
if (MatchesPossibleNumberAndNationalNumber(*matcher_api_, short_number,
phone_metadata->toll_free())) {
return ShortNumberInfo::TOLL_FREE;
}
if (IsEmergencyNumber(short_number, region_dialing_from)) {
@ -175,21 +249,18 @@ ShortNumberInfo::ShortNumberCost ShortNumberInfo::GetExpectedCost(
const PhoneNumber& number) const {
list<string> region_codes;
phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
&region_codes);
&region_codes);
if (region_codes.size() == 0) {
return ShortNumberInfo::UNKNOWN_COST;
}
string short_number;
phone_util_.GetNationalSignificantNumber(number, &short_number);
if (region_codes.size() == 1) {
return GetExpectedCostForRegion(short_number, region_codes.front());
return GetExpectedCostForRegion(number, region_codes.front());
}
ShortNumberInfo::ShortNumberCost cost =
ShortNumberInfo::TOLL_FREE;
ShortNumberInfo::ShortNumberCost cost = ShortNumberInfo::TOLL_FREE;
for (list<string>::const_iterator it = region_codes.begin();
it != region_codes.end(); ++it) {
ShortNumberInfo::ShortNumberCost cost_for_region =
GetExpectedCostForRegion(short_number, *it);
GetExpectedCostForRegion(number, *it);
switch (cost_for_region) {
case ShortNumberInfo::PREMIUM_RATE:
return ShortNumberInfo::PREMIUM_RATE;
@ -217,19 +288,22 @@ void ShortNumberInfo::GetRegionCodeForShortNumberFromRegionList(
string* region_code) const {
if (region_codes.size() == 0) {
region_code->assign(RegionCode::GetUnknown());
return;
} else if (region_codes.size() == 1) {
region_code->assign(region_codes.front());
return;
}
string national_number;
phone_util_.GetNationalSignificantNumber(number, &national_number);
for (list<string>::const_iterator it = region_codes.begin();
it != region_codes.end(); ++it) {
it != region_codes.end(); ++it) {
const PhoneMetadata* phone_metadata = GetMetadataForRegion(*it);
if (phone_metadata != NULL &&
phone_util_.IsNumberMatchingDesc(national_number,
phone_metadata->short_code())) {
MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
phone_metadata->short_code())) {
// The number is valid for this region.
region_code->assign(*it);
return;
}
}
region_code->assign(RegionCode::GetUnknown());
@ -302,34 +376,26 @@ bool ShortNumberInfo::MatchesEmergencyNumberHelper(const string& number,
if (!metadata || !metadata->has_emergency()) {
return false;
}
const scoped_ptr<const AbstractRegExpFactory> regexp_factory(
new RegExpFactory());
const scoped_ptr<const RegExp> emergency_number_pattern(
regexp_factory->CreateRegExp(
metadata->emergency().national_number_pattern()));
phone_util_.NormalizeDigitsOnly(&extracted_number);
const scoped_ptr<RegExpInput> normalized_number_input(
regexp_factory->CreateInput(extracted_number));
return (!allow_prefix_match ||
regions_where_emergency_numbers_must_be_exact_->find(region_code)
!= regions_where_emergency_numbers_must_be_exact_->end())
? emergency_number_pattern->FullMatch(extracted_number)
: emergency_number_pattern->Consume(normalized_number_input.get());
bool allow_prefix_match_for_region =
allow_prefix_match &&
regions_where_emergency_numbers_must_be_exact_->find(region_code) ==
regions_where_emergency_numbers_must_be_exact_->end();
return matcher_api_->MatchesNationalNumber(
extracted_number, metadata->emergency(), allow_prefix_match_for_region);
}
bool ShortNumberInfo::IsCarrierSpecific(const PhoneNumber& number) const {
list<string> region_codes;
phone_util_.GetRegionCodesForCountryCallingCode(number.country_code(),
&region_codes);
&region_codes);
string region_code;
GetRegionCodeForShortNumberFromRegionList(number,
region_codes, &region_code);
GetRegionCodeForShortNumberFromRegionList(number, region_codes, &region_code);
string national_number;
phone_util_.GetNationalSignificantNumber(number, &national_number);
const PhoneMetadata* phone_metadata = GetMetadataForRegion(region_code);
return phone_metadata &&
phone_util_.IsNumberMatchingDesc(national_number,
MatchesPossibleNumberAndNationalNumber(*matcher_api_, national_number,
phone_metadata->carrier_specific());
}


+ 57
- 1
cpp/src/phonenumbers/shortnumberinfo.h View File

@ -26,7 +26,6 @@
#include "phonenumbers/base/basictypes.h"
#include "phonenumbers/base/memory/scoped_ptr.h"
#include "phonenumbers/phonemetadata.pb.h"
namespace i18n {
namespace phonenumbers {
@ -36,12 +35,15 @@ using std::map;
using std::set;
using std::string;
class MatcherApi;
class PhoneMetadata;
class PhoneNumber;
class PhoneNumberUtil;
class ShortNumberInfo {
public:
ShortNumberInfo();
~ShortNumberInfo();
// Cost categories of short numbers.
enum ShortNumberCost {
@ -51,6 +53,12 @@ class ShortNumberInfo {
UNKNOWN_COST
};
// DEPRECATED: Anyone who was using it and passing in a string with whitespace
// (or other formatting characters) would have been getting the wrong result.
// You should parse the string to PhoneNumber and use the method
// IsPossibleShortNumberForRegion(PhoneNumber, String). This method will be
// removed in the next release.
//
// Check whether a short number is a possible number when dialled from a
// region, 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
@ -59,12 +67,26 @@ class ShortNumberInfo {
const string& short_number,
const string& region_dialing_from) const;
// Check whether a short number is a possible number when dialled from a
// region, 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
// IsValidShortNumberForRegion.
bool IsPossibleShortNumberForRegion(
const PhoneNumber& short_number,
const string& region_dialing_from) const;
// Check whether a short number is a possible number. If a country calling
// code is shared by multiple regions, this returns true if it's possible in
// any of them. This provides a more lenient check than IsValidShortNumber.
// See IsPossibleShortNumberForRegion for details.
bool IsPossibleShortNumber(const PhoneNumber& number) const;
// DEPRECATED: Anyone who was using it and passing in a string with whitespace
// (or other formatting characters) would have been getting the wrong result.
// You should parse the string to PhoneNumber and use the method
// IsValidShortNumberForRegion(PhoneNumber, String). This method will be
// removed in the next release.
//
// Tests whether a short number matches a valid pattern in a region. Note
// that this doesn't verify the number is actually in use, which is
// impossible to tell by just looking at the number itself.
@ -72,6 +94,13 @@ class ShortNumberInfo {
const string& short_number,
const string& region_dialing_from) const;
// Tests whether a short number matches a valid pattern in a region. Note
// that this doesn't verify the number is actually in use, which is
// impossible to tell by just looking at the number itself.
bool IsValidShortNumberForRegion(
const PhoneNumber& short_number,
const string& region_dialing_from) const;
// Tests whether a short number matches a valid pattern. If a country calling
// code is shared by multiple regions, this returns true if it's valid in any
// of them. Note that this doesn't verify the number is actually in use,
@ -79,6 +108,12 @@ class ShortNumberInfo {
// IsValidShortNumberForRegion for details.
bool IsValidShortNumber(const PhoneNumber& number) const;
// DEPRECATED: Anyone who was using it and passing in a string with whitespace
// (or other formatting characters) would have been getting the wrong result.
// You should parse the string to PhoneNumber and use the method
// GetExpectedCostForRegion(PhoneNumber, String). This method will be
// removed in the next release.
//
// Gets the expected cost category of a short number when dialled from a
// region (however, nothing is implied about its validity). If it is
// important that the number is valid, then its validity must first be
@ -97,6 +132,26 @@ class ShortNumberInfo {
const string& short_number,
const string& region_dialing_from) const;
// Gets the expected cost category of a short number when dialled from a
// region (however, nothing is implied about its validity). If it is
// important that the number is valid, then its validity must first be
// checked using IsValidShortNumberForRegion. Note that emergency numbers are
// always considered toll-free. Example usage:
//
// PhoneNumber number;
// phone_util.Parse("110", "US", &number);
// ...
// string region_code("CA");
// ShortNumberInfo short_info;
// if (short_info.IsValidShortNumberForRegion(number, region_code)) {
// ShortNumberInfo::ShortNumberCost cost =
// short_info.GetExpectedCostForRegion(number, region_code);
// // Do something with the cost information here.
// }
ShortNumberCost GetExpectedCostForRegion(
const PhoneNumber& short_number,
const string& region_dialing_from) const;
// Gets the expected cost category of a short number (however, nothing is
// implied about its validity). If the country calling code is unique to a
// region, this method behaves exactly the same as GetExpectedCostForRegion.
@ -147,6 +202,7 @@ class ShortNumberInfo {
private:
const PhoneNumberUtil& phone_util_;
const scoped_ptr<const MatcherApi> matcher_api_;
// A mapping from a RegionCode to the PhoneMetadata for that region.
scoped_ptr<map<string, PhoneMetadata> >


+ 629
- 610
cpp/src/phonenumbers/test_metadata.cc
File diff suppressed because it is too large
View File


+ 131
- 76
cpp/test/phonenumbers/shortnumberinfo_test.cc View File

@ -22,8 +22,10 @@
#include <gtest/gtest.h>
#include "phonenumbers/base/logging.h"
#include "phonenumbers/default_logger.h"
#include "phonenumbers/phonenumberutil.h"
#include "phonenumbers/stringutil.h"
#include "phonenumbers/test_util.h"
namespace i18n {
@ -31,10 +33,19 @@ namespace phonenumbers {
class ShortNumberInfoTest : public testing::Test {
protected:
PhoneNumber ParseNumberForTesting(const string& number,
const string& region_code) {
PhoneNumber phone_number;
CHECK_EQ(phone_util_.Parse(number, region_code, &phone_number),
PhoneNumberUtil::NO_PARSING_ERROR);
return phone_number;
}
ShortNumberInfoTest() : short_info_() {
PhoneNumberUtil::GetInstance()->SetLogger(new StdoutLogger());
}
const PhoneNumberUtil phone_util_;
const ShortNumberInfo short_info_;
private:
@ -46,15 +57,13 @@ TEST_F(ShortNumberInfoTest, IsPossibleShortNumber) {
possible_number.set_country_code(33);
possible_number.set_national_number(123456ULL);
EXPECT_TRUE(short_info_.IsPossibleShortNumber(possible_number));
EXPECT_TRUE(short_info_.IsPossibleShortNumberForRegion("123456",
RegionCode::FR()));
EXPECT_TRUE(short_info_.IsPossibleShortNumberForRegion(
ParseNumberForTesting("123456", RegionCode::FR()), RegionCode::FR()));
PhoneNumber impossible_number;
impossible_number.set_country_code(33);
impossible_number.set_national_number(9ULL);
EXPECT_FALSE(short_info_.IsPossibleShortNumber(impossible_number));
EXPECT_FALSE(short_info_.IsPossibleShortNumberForRegion("9",
RegionCode::FR()));
// Note that GB and GG share the country calling code 44, and that this
// number is possible but not valid.
@ -69,15 +78,15 @@ TEST_F(ShortNumberInfoTest, IsValidShortNumber) {
valid_number.set_country_code(33);
valid_number.set_national_number(1010ULL);
EXPECT_TRUE(short_info_.IsValidShortNumber(valid_number));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion("1010",
RegionCode::FR()));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion(
ParseNumberForTesting("1010", RegionCode::FR()), RegionCode::FR()));
PhoneNumber invalid_number;
invalid_number.set_country_code(33);
invalid_number.set_national_number(123456ULL);
EXPECT_FALSE(short_info_.IsValidShortNumber(invalid_number));
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion("123456",
RegionCode::FR()));
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion(
ParseNumberForTesting("123456", RegionCode::FR()), RegionCode::FR()));
// Note that GB and GG share the country calling code 44.
PhoneNumber shared_number;
@ -90,71 +99,80 @@ TEST_F(ShortNumberInfoTest, GetExpectedCost) {
uint64 national_number;
const string& premium_rate_example =
short_info_.GetExampleShortNumberForCost(
RegionCode::FR(), ShortNumberInfo::ShortNumberCost::PREMIUM_RATE);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::PREMIUM_RATE,
short_info_.GetExpectedCostForRegion(premium_rate_example,
RegionCode::FR()));
RegionCode::FR(), ShortNumberInfo::PREMIUM_RATE);
EXPECT_EQ(ShortNumberInfo::PREMIUM_RATE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(premium_rate_example, RegionCode::FR()),
RegionCode::FR()));
PhoneNumber premium_rate_number;
premium_rate_number.set_country_code(33);
safe_strtou64(premium_rate_example, &national_number);
premium_rate_number.set_national_number(national_number);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::PREMIUM_RATE,
EXPECT_EQ(ShortNumberInfo::PREMIUM_RATE,
short_info_.GetExpectedCost(premium_rate_number));
const string& standard_rate_example =
short_info_.GetExampleShortNumberForCost(
RegionCode::FR(), ShortNumberInfo::ShortNumberCost::STANDARD_RATE);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::STANDARD_RATE,
short_info_.GetExpectedCostForRegion(standard_rate_example,
RegionCode::FR()));
RegionCode::FR(), ShortNumberInfo::STANDARD_RATE);
EXPECT_EQ(ShortNumberInfo::STANDARD_RATE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(standard_rate_example, RegionCode::FR()),
RegionCode::FR()));
PhoneNumber standard_rate_number;
standard_rate_number.set_country_code(33);
safe_strtou64(standard_rate_example, &national_number);
standard_rate_number.set_national_number(national_number);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::STANDARD_RATE,
EXPECT_EQ(ShortNumberInfo::STANDARD_RATE,
short_info_.GetExpectedCost(standard_rate_number));
const string& toll_free_example =
short_info_.GetExampleShortNumberForCost(
RegionCode::FR(), ShortNumberInfo::ShortNumberCost::TOLL_FREE);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
short_info_.GetExpectedCostForRegion(toll_free_example,
RegionCode::FR()));
RegionCode::FR(), ShortNumberInfo::TOLL_FREE);
EXPECT_EQ(ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(toll_free_example, RegionCode::FR()),
RegionCode::FR()));
PhoneNumber toll_free_number;
toll_free_number.set_country_code(33);
safe_strtou64(toll_free_example, &national_number);
toll_free_number.set_national_number(national_number);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
EXPECT_EQ(ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCost(toll_free_number));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion("12345", RegionCode::FR()));
EXPECT_EQ(
ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("12345", RegionCode::FR()), RegionCode::FR()));
PhoneNumber unknown_cost_number;
unknown_cost_number.set_country_code(33);
unknown_cost_number.set_national_number(12345ULL);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
EXPECT_EQ(ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCost(unknown_cost_number));
// Test that an invalid number may nevertheless have a cost other than
// UNKNOWN_COST.
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion("116123",
RegionCode::FR()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
short_info_.GetExpectedCostForRegion("116123", RegionCode::FR()));
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion(
ParseNumberForTesting("116123", RegionCode::FR()), RegionCode::FR()));
EXPECT_EQ(
ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("116123", RegionCode::FR()), RegionCode::FR()));
PhoneNumber invalid_number;
invalid_number.set_country_code(33);
invalid_number.set_national_number(116123ULL);
EXPECT_FALSE(short_info_.IsValidShortNumber(invalid_number));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
EXPECT_EQ(ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCost(invalid_number));
// Test a nonexistent country code.
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion("911", RegionCode::ZZ()));
EXPECT_EQ(
ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("911", RegionCode::US()), RegionCode::ZZ()));
unknown_cost_number.Clear();
unknown_cost_number.set_country_code(123);
unknown_cost_number.set_national_number(911ULL);
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
EXPECT_EQ(ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCost(unknown_cost_number));
}
@ -181,43 +199,64 @@ TEST_F(ShortNumberInfoTest, GetExpectedCostForSharedCountryCallingCode) {
EXPECT_TRUE(short_info_.IsValidShortNumber(ambiguous_toll_free_number));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion(
ambiguous_premium_rate_string, RegionCode::AU()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::PREMIUM_RATE,
short_info_.GetExpectedCostForRegion(ambiguous_premium_rate_string,
RegionCode::AU()));
ParseNumberForTesting(ambiguous_premium_rate_string, RegionCode::AU()),
RegionCode::AU()));
EXPECT_EQ(ShortNumberInfo::PREMIUM_RATE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(ambiguous_premium_rate_string,
RegionCode::AU()),
RegionCode::AU()));
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion(
ambiguous_premium_rate_string, RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(ambiguous_premium_rate_string,
RegionCode::CX()));
ParseNumberForTesting(ambiguous_premium_rate_string, RegionCode::CX()),
RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(ambiguous_premium_rate_string,
RegionCode::CX()),
RegionCode::CX()));
// PREMIUM_RATE takes precedence over UNKNOWN_COST.
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::PREMIUM_RATE,
EXPECT_EQ(ShortNumberInfo::PREMIUM_RATE,
short_info_.GetExpectedCost(ambiguous_premium_rate_number));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion(
ambiguous_standard_rate_string, RegionCode::AU()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::STANDARD_RATE,
short_info_.GetExpectedCostForRegion(ambiguous_standard_rate_string,
RegionCode::AU()));
ParseNumberForTesting(ambiguous_standard_rate_string, RegionCode::AU()),
RegionCode::AU()));
EXPECT_EQ(ShortNumberInfo::STANDARD_RATE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(ambiguous_standard_rate_string,
RegionCode::AU()),
RegionCode::AU()));
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion(
ambiguous_standard_rate_string, RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(ambiguous_standard_rate_string,
RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
ParseNumberForTesting(ambiguous_standard_rate_string, RegionCode::CX()),
RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(ambiguous_standard_rate_string,
RegionCode::CX()),
RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCost(ambiguous_standard_rate_number));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion(
ambiguous_toll_free_string, RegionCode::AU()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
short_info_.GetExpectedCostForRegion(ambiguous_toll_free_string,
ParseNumberForTesting(ambiguous_toll_free_string, RegionCode::AU()),
RegionCode::AU()));
EXPECT_EQ(
ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(ambiguous_toll_free_string, RegionCode::AU()),
RegionCode::AU()));
EXPECT_FALSE(short_info_.IsValidShortNumberForRegion(
ambiguous_toll_free_string, RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(ambiguous_toll_free_string,
ParseNumberForTesting(ambiguous_toll_free_string, RegionCode::CX()),
RegionCode::CX()));
EXPECT_EQ(
ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting(ambiguous_toll_free_string, RegionCode::CX()),
RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
EXPECT_EQ(ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCost(ambiguous_toll_free_number));
}
@ -231,15 +270,15 @@ TEST_F(ShortNumberInfoTest, GetExampleShortNumber) {
TEST_F(ShortNumberInfoTest, GetExampleShortNumberForCost) {
EXPECT_EQ("3010",
short_info_.GetExampleShortNumberForCost(RegionCode::FR(),
ShortNumberInfo::ShortNumberCost::TOLL_FREE));
ShortNumberInfo::TOLL_FREE));
EXPECT_EQ("1023",
short_info_.GetExampleShortNumberForCost(RegionCode::FR(),
ShortNumberInfo::ShortNumberCost::STANDARD_RATE));
ShortNumberInfo::STANDARD_RATE));
EXPECT_EQ("42000",
short_info_.GetExampleShortNumberForCost(RegionCode::FR(),
ShortNumberInfo::ShortNumberCost::PREMIUM_RATE));
ShortNumberInfo::PREMIUM_RATE));
EXPECT_EQ("", short_info_.GetExampleShortNumberForCost(RegionCode::FR(),
ShortNumberInfo::ShortNumberCost::UNKNOWN_COST));
ShortNumberInfo::UNKNOWN_COST));
}
TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumber_US) {
@ -380,18 +419,26 @@ TEST_F(ShortNumberInfoTest, EmergencyNumberForSharedCountryCallingCode) {
// Test the emergency number 112, which is valid in both Australia and the
// Christmas Islands.
EXPECT_TRUE(short_info_.IsEmergencyNumber("112", RegionCode::AU()));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion("112", RegionCode::AU()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
short_info_.GetExpectedCostForRegion("112", RegionCode::AU()));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion(
ParseNumberForTesting("112", RegionCode::AU()), RegionCode::AU()));
EXPECT_EQ(
ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("112", RegionCode::AU()), RegionCode::AU()));
EXPECT_TRUE(short_info_.IsEmergencyNumber("112", RegionCode::CX()));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion("112", RegionCode::CX()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
short_info_.GetExpectedCostForRegion("112", RegionCode::CX()));
EXPECT_TRUE(short_info_.IsValidShortNumberForRegion(
ParseNumberForTesting("112", RegionCode::CX()), RegionCode::CX()));
EXPECT_EQ(
ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("112", RegionCode::CX()), RegionCode::CX()));
PhoneNumber shared_emergency_number;
shared_emergency_number.set_country_code(61);
shared_emergency_number.set_national_number(112ULL);
EXPECT_TRUE(short_info_.IsValidShortNumber(shared_emergency_number));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
EXPECT_EQ(ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCost(shared_emergency_number));
}
@ -399,14 +446,22 @@ TEST_F(ShortNumberInfoTest, OverlappingNANPANumber) {
// 211 is an emergency number in Barbados, while it is a toll-free
// information line in Canada and the USA.
EXPECT_TRUE(short_info_.IsEmergencyNumber("211", RegionCode::BB()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::TOLL_FREE,
short_info_.GetExpectedCostForRegion("211", RegionCode::BB()));
EXPECT_EQ(
ShortNumberInfo::TOLL_FREE,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("211", RegionCode::BB()), RegionCode::BB()));
EXPECT_FALSE(short_info_.IsEmergencyNumber("211", RegionCode::US()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion("211", RegionCode::US()));
EXPECT_EQ(
ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("211", RegionCode::US()), RegionCode::US()));
EXPECT_FALSE(short_info_.IsEmergencyNumber("211", RegionCode::CA()));
EXPECT_EQ(ShortNumberInfo::ShortNumberCost::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion("211", RegionCode::CA()));
EXPECT_EQ(
ShortNumberInfo::UNKNOWN_COST,
short_info_.GetExpectedCostForRegion(
ParseNumberForTesting("211", RegionCode::CA()), RegionCode::CA()));
}
} // namespace phonenumbers


+ 12
- 0
cpp/test/phonenumbers/test_util.h View File

@ -57,6 +57,10 @@ class RegionCode {
return "AE";
}
static const char* AM() {
return "AM";
}
static const char* AO() {
return "AO";
}
@ -73,6 +77,10 @@ class RegionCode {
return "AU";
}
static const char* BB() {
return "BB";
}
static const char* BR() {
return "BR";
}
@ -105,6 +113,10 @@ class RegionCode {
return "CS";
}
static const char* CX() {
return "CX";
}
static const char* DE() {
return "DE";
}


+ 223
- 55
java/libphonenumber/src/com/google/i18n/phonenumbers/ShortNumberInfo.java View File

@ -16,13 +16,18 @@
package com.google.i18n.phonenumbers;
import com.google.i18n.phonenumbers.internal.MatcherApi;
import com.google.i18n.phonenumbers.internal.RegexBasedMatcher;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -40,7 +45,7 @@ public class ShortNumberInfo {
private static final Logger logger = Logger.getLogger(ShortNumberInfo.class.getName());
private static final ShortNumberInfo INSTANCE =
new ShortNumberInfo(PhoneNumberUtil.getInstance());
new ShortNumberInfo(RegexBasedMatcher.create());
// In these countries, if extra digits are added to an emergency number, it no longer connects
// to the emergency service.
@ -65,11 +70,34 @@ public class ShortNumberInfo {
return INSTANCE;
}
private final PhoneNumberUtil phoneUtil;
// MatcherApi supports the basic matching method for checking if a given national number matches
// a national number patten or a possible number patten defined in the given
// {@code PhoneNumberDesc}.
private final MatcherApi matcherApi;
// 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<Integer, List<String>> countryCallingCodeToRegionCodeMap;
// @VisibleForTesting
ShortNumberInfo(PhoneNumberUtil util) {
phoneUtil = util;
ShortNumberInfo(MatcherApi matcherApi) {
this.matcherApi = matcherApi;
// TODO: Create ShortNumberInfo for a given map
this.countryCallingCodeToRegionCodeMap =
CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap();
}
/**
* Returns a list with the region codes that match the specific country calling code. For
* non-geographical country calling codes, the region code 001 is returned. Also, in the case
* of no region code being found, an empty list is returned.
*/
private List<String> getRegionCodesForCountryCode(int countryCallingCode) {
List<String> regionCodes = countryCallingCodeToRegionCodeMap.get(countryCallingCode);
return Collections.unmodifiableList(regionCodes == null ? new ArrayList<String>(0)
: regionCodes);
}
/**
@ -80,32 +108,55 @@ public class ShortNumberInfo {
* @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
* @deprecated Anyone who was using it and passing in a string with whitespace (or other
* formatting characters) would have been getting the wrong result. You should parse
* the string to PhoneNumber and use the method
* {@code #isPossibleShortNumberForRegion(PhoneNumber, String)}. This method will be
* removed in the next release.
*/
@Deprecated
public boolean isPossibleShortNumberForRegion(String shortNumber, String regionDialingFrom) {
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
if (phoneMetadata == null) {
return false;
}
PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc();
return phoneUtil.isNumberPossibleForDesc(shortNumber, generalDesc);
return matcherApi.matchesPossibleNumber(shortNumber, phoneMetadata.getGeneralDesc());
}
/**
* Check whether a short number is a possible number when dialed from the given region. This
* provides a more lenient check than {@link #isValidShortNumberForRegion}.
*
* @param number the short number to check
* @param regionDialingFrom the region from which the number is dialed
* @return whether the number is a possible short number
*/
public boolean isPossibleShortNumberForRegion(PhoneNumber number, String regionDialingFrom) {
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
if (phoneMetadata == null) {
return false;
}
return matcherApi.matchesPossibleNumber(getNationalSignificantNumber(number),
phoneMetadata.getGeneralDesc());
}
/**
* Check whether a short number is a possible number. If a country calling code is shared by
* multiple regions, this returns true if it's possible in any of them. This provides a more
* lenient check than {@link #isValidShortNumber}. See {@link
* #isPossibleShortNumberForRegion(String, String)} for details.
* #isPossibleShortNumberForRegion(PhoneNumber, String)} for details.
*
* @param number the short number to check
* @return whether the number is a possible short number
*/
public boolean isPossibleShortNumber(PhoneNumber number) {
List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
String shortNumber = phoneUtil.getNationalSignificantNumber(number);
List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
String shortNumber = getNationalSignificantNumber(number);
for (String region : regionCodes) {
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(region);
if (phoneUtil.isNumberPossibleForDesc(shortNumber, phoneMetadata.getGeneralDesc())) {
if (matcherApi.matchesPossibleNumber(shortNumber, phoneMetadata.getGeneralDesc())) {
return true;
}
}
@ -120,7 +171,13 @@ public class ShortNumberInfo {
* @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
* @deprecated Anyone who was using it and passing in a string with whitespace (or other
* formatting characters) would have been getting the wrong result. You should parse
* the string to PhoneNumber and use the method
* {@code #isValidShortNumberForRegion(PhoneNumber, String)}. This method will be
* removed in the next release.
*/
@Deprecated
public boolean isValidShortNumberForRegion(String shortNumber, String regionDialingFrom) {
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
@ -128,38 +185,55 @@ public class ShortNumberInfo {
return false;
}
PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc();
if (!generalDesc.hasNationalNumberPattern() ||
!phoneUtil.isNumberMatchingDesc(shortNumber, generalDesc)) {
if (!matchesPossibleNumberAndNationalNumber(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 matchesPossibleNumberAndNationalNumber(shortNumber, shortNumberDesc);
}
/**
* Tests whether a short number matches a valid pattern in a region. 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 number the short number for which we want to test the validity
* @param regionDialingFrom the region from which the number is dialed
* @return whether the short number matches a valid pattern
*/
public boolean isValidShortNumberForRegion(PhoneNumber number, String regionDialingFrom) {
PhoneMetadata phoneMetadata =
MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
if (phoneMetadata == null) {
return false;
}
String shortNumber = getNationalSignificantNumber(number);
PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc();
if (!matchesPossibleNumberAndNationalNumber(shortNumber, generalDesc)) {
return false;
}
return phoneUtil.isNumberMatchingDesc(shortNumber, shortNumberDesc);
PhoneNumberDesc shortNumberDesc = phoneMetadata.getShortCode();
return matchesPossibleNumberAndNationalNumber(shortNumber, shortNumberDesc);
}
/**
* Tests whether a short number matches a valid pattern. If a country calling code is shared by
* multiple regions, this returns true if it's valid in any of them. Note that this doesn't verify
* the number is actually in use, which is impossible to tell by just looking at the number
* itself. See {@link #isValidShortNumberForRegion(String, String)} for details.
* itself. See {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details.
*
* @param number the short number for which we want to test the validity
* @return whether the short number matches a valid pattern
*/
public boolean isValidShortNumber(PhoneNumber number) {
List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
String shortNumber = phoneUtil.getNationalSignificantNumber(number);
List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes);
if (regionCodes.size() > 1 && regionCode != null) {
// If a matching region had been found for the phone number from among two or more regions,
// then we have already implicitly verified its validity for that region.
return true;
}
return isValidShortNumberForRegion(shortNumber, regionCode);
return isValidShortNumberForRegion(number, regionCode);
}
/**
@ -183,7 +257,13 @@ public class ShortNumberInfo {
* @return the expected cost category for that region 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.
* @deprecated Anyone who was using it and passing in a string with whitespace (or other
* formatting characters) would have been getting the wrong result. You should parse
* the string to PhoneNumber and use the method
* {@code #getExpectedCostForRegion(PhoneNumber, String)}. This method will be
* removed in the next release.
*/
@Deprecated
public ShortNumberCost getExpectedCostForRegion(String shortNumber, String regionDialingFrom) {
// Note that regionDialingFrom may be null, in which case phoneMetadata will also be null.
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(
@ -194,13 +274,64 @@ public class ShortNumberInfo {
// 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(shortNumber, phoneMetadata.getPremiumRate())) {
if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getPremiumRate())) {
return ShortNumberCost.PREMIUM_RATE;
}
if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getStandardRate())) {
return ShortNumberCost.STANDARD_RATE;
}
if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getTollFree())) {
return ShortNumberCost.TOLL_FREE;
}
if (isEmergencyNumber(shortNumber, regionDialingFrom)) {
// Emergency numbers are implicitly toll-free.
return ShortNumberCost.TOLL_FREE;
}
return ShortNumberCost.UNKNOWN_COST;
}
/**
* Gets the expected cost category of a short number when dialed from a region (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 #isValidShortNumberForRegion}. Note that emergency numbers
* are always considered toll-free. Example usage:
* <pre>{@code
* // The region for which the number was parsed and the region we subsequently check against
* // need not be the same. Here we parse the number in the US and check it for Canada.
* PhoneNumber number = phoneUtil.parse("110", "US");
* ...
* String regionCode = "CA";
* ShortNumberInfo shortInfo = ShortNumberInfo.getInstance();
* if (shortInfo.isValidShortNumberForRegion(shortNumber, regionCode)) {
* ShortNumberCost cost = shortInfo.getExpectedCostForRegion(number, regionCode);
* // Do something with the cost information here.
* }}</pre>
*
* @param number the short number for which we want to know the expected cost category
* @param regionDialingFrom the region from which the number is dialed
* @return the expected cost category for that region 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 getExpectedCostForRegion(PhoneNumber number, String regionDialingFrom) {
// Note that regionDialingFrom may be null, in which case phoneMetadata will also be null.
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(
regionDialingFrom);
if (phoneMetadata == null) {
return ShortNumberCost.UNKNOWN_COST;
}
String shortNumber = 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 (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getPremiumRate())) {
return ShortNumberCost.PREMIUM_RATE;
}
if (phoneUtil.isNumberMatchingDesc(shortNumber, phoneMetadata.getStandardRate())) {
if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getStandardRate())) {
return ShortNumberCost.STANDARD_RATE;
}
if (phoneUtil.isNumberMatchingDesc(shortNumber, phoneMetadata.getTollFree())) {
if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getTollFree())) {
return ShortNumberCost.TOLL_FREE;
}
if (isEmergencyNumber(shortNumber, regionDialingFrom)) {
@ -213,36 +344,35 @@ public class ShortNumberInfo {
/**
* Gets the expected cost category of a short number (however, nothing is implied about its
* validity). If the country calling code is unique to a region, this method behaves exactly the
* same as {@link #getExpectedCostForRegion(String, String)}. However, if the country calling
* code is shared by multiple regions, then it returns the highest cost in the sequence
* same as {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the country
* calling code is shared by multiple regions, then it returns the highest cost in the sequence
* PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, TOLL_FREE. The reason for the position of
* UNKNOWN_COST in this order is that if a number is UNKNOWN_COST in one region but STANDARD_RATE
* or TOLL_FREE in another, its expected cost cannot be estimated as one of the latter since it
* might be a PREMIUM_RATE number.
*
* For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, the expected cost
* returned by this method will be STANDARD_RATE, since the NANPA countries share the same country
* calling code.
*
* <p>
* For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, the expected
* cost returned by this method will be STANDARD_RATE, since the NANPA countries share the same
* country calling code.
* <p>
* Note: If the region from which the number is dialed is known, it is highly preferable to call
* {@link #getExpectedCostForRegion(String, String)} instead.
* {@link #getExpectedCostForRegion(PhoneNumber, String)} instead.
*
* @param number the short number for which we want to know the expected cost category
* @return the highest expected cost category of the short number in the region(s) with the given
* country calling code
*/
public ShortNumberCost getExpectedCost(PhoneNumber number) {
List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
if (regionCodes.size() == 0) {
return ShortNumberCost.UNKNOWN_COST;
}
String shortNumber = phoneUtil.getNationalSignificantNumber(number);
if (regionCodes.size() == 1) {
return getExpectedCostForRegion(shortNumber, regionCodes.get(0));
return getExpectedCostForRegion(number, regionCodes.get(0));
}
ShortNumberCost cost = ShortNumberCost.TOLL_FREE;
for (String regionCode : regionCodes) {
ShortNumberCost costForRegion = getExpectedCostForRegion(shortNumber, regionCode);
ShortNumberCost costForRegion = getExpectedCostForRegion(number, regionCode);
switch (costForRegion) {
case PREMIUM_RATE:
return ShortNumberCost.PREMIUM_RATE;
@ -274,11 +404,11 @@ public class ShortNumberInfo {
} else if (regionCodes.size() == 1) {
return regionCodes.get(0);
}
String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
String nationalNumber = getNationalSignificantNumber(number);
for (String regionCode : regionCodes) {
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
if (phoneMetadata != null &&
phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getShortCode())) {
if (phoneMetadata != null
&& matchesPossibleNumberAndNationalNumber(nationalNumber, phoneMetadata.getShortCode())) {
// The number is valid for this region.
return regionCode;
}
@ -349,11 +479,14 @@ public class ShortNumberInfo {
}
/**
* Returns true if the number might be used to connect to an emergency service in the given
* region.
*
* This method takes into account cases where the number might contain formatting, or might have
* additional digits appended (when it is okay to do that in the region specified).
* Returns true if the given number, exactly as dialed, might be used to connect to an emergency
* service in the given region.
* <p>
* This method accepts a string, rather than a PhoneNumber, because it needs to distinguish
* cases such as "+1 911" and "911", where the former may not connect to an emergency service in
* all cases but the latter would. This method takes into account cases where the number might
* contain formatting, or might have additional digits appended (when it is okay to do that in
* the specified region).
*
* @param number the phone number to test
* @param regionCode the region where the phone number is being dialed
@ -364,10 +497,12 @@ public class ShortNumberInfo {
}
/**
* Returns true if the number exactly matches an emergency service number in the given region.
*
* Returns true if the given number exactly matches an emergency service number in the given
* region.
* <p>
* This method takes into account cases where the number might contain formatting, but doesn't
* allow additional digits to be appended.
* allow additional digits to be appended. Note that {@code isEmergencyNumber(number, region)}
* implies {@code connectsToEmergencyNumber(number, region)}.
*
* @param number the phone number to test
* @param regionCode the region where the phone number is being dialed
@ -390,12 +525,13 @@ public class ShortNumberInfo {
if (metadata == null || !metadata.hasEmergency()) {
return false;
}
Pattern emergencyNumberPattern =
Pattern.compile(metadata.getEmergency().getNationalNumberPattern());
String normalizedNumber = PhoneNumberUtil.normalizeDigitsOnly(number);
return (!allowPrefixMatch || REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.contains(regionCode))
? emergencyNumberPattern.matcher(normalizedNumber).matches()
: emergencyNumberPattern.matcher(normalizedNumber).lookingAt();
PhoneNumberDesc emergencyDesc = metadata.getEmergency();
boolean allowPrefixMatchForRegion =
allowPrefixMatch && !REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.contains(regionCode);
return matcherApi.matchesNationalNumber(normalizedNumber, emergencyDesc,
allowPrefixMatchForRegion);
}
/**
@ -409,11 +545,43 @@ public class ShortNumberInfo {
* number).
*/
public boolean isCarrierSpecific(PhoneNumber number) {
List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode());
List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes);
String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
String nationalNumber = getNationalSignificantNumber(number);
PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
return (phoneMetadata != null) &&
(phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getCarrierSpecific()));
return (phoneMetadata != null)
&& (matchesPossibleNumberAndNationalNumber(nationalNumber,
phoneMetadata.getCarrierSpecific()));
}
/**
* Gets the national significant number of the a phone number. Note a national significant number
* doesn't contain a national prefix or any formatting.
* <p>
* This is a temporary duplicate of the {@code getNationalSignificantNumber} method from
* {@code PhoneNumberUtil}. Ultimately a canonical static version should exist in a separate
* utility class (to prevent {@code ShortNumberInfo} needing to depend on PhoneNumberUtil).
*
* @param number the phone number for which the national significant number is needed
* @return the national significant number of the PhoneNumber object passed in
*/
private static 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();
}
// TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth keeping
// this performance optimization, and if so move this into the matcher implementation.
private boolean matchesPossibleNumberAndNationalNumber(String number,
PhoneNumberDesc numberDesc) {
return matcherApi.matchesPossibleNumber(number, numberDesc)
&& matcherApi.matchesNationalNumber(number, numberDesc, false);
}
}

+ 22
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/internal/MatcherApi.java View File

@ -0,0 +1,22 @@
package com.google.i18n.phonenumbers.internal;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
/**
* Internal phonenumber matching API used to isolate the underlying implementation of the
* matcher and allow different implementations to be swapped in easily.
*/
public interface MatcherApi {
/**
* Returns whether the given national number (a string containing only decimal digits) matches
* the national number pattern defined in the given {@code PhoneNumberDesc} message.
*/
boolean matchesNationalNumber(String nationalNumber, PhoneNumberDesc numberDesc,
boolean allowPrefixMatch);
/**
* Returns whether the given national number (a string containing only decimal digits) matches
* the possible number pattern defined in the given {@code PhoneNumberDesc} message.
*/
boolean matchesPossibleNumber(String nationalNumber, PhoneNumberDesc numberDesc);
}

+ 36
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/internal/RegexBasedMatcher.java View File

@ -0,0 +1,36 @@
package com.google.i18n.phonenumbers.internal;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import com.google.i18n.phonenumbers.RegexCache;
import java.util.regex.Matcher;
/**
* Implementation of the matcher API using the regular expressions in the PhoneNumberDesc
* proto message to match numbers.
*/
public final class RegexBasedMatcher implements MatcherApi {
public static MatcherApi create() {
return new RegexBasedMatcher();
}
private final RegexCache regexCache = new RegexCache(100);
private RegexBasedMatcher() {}
// @Override
public boolean matchesNationalNumber(String nationalNumber, PhoneNumberDesc numberDesc,
boolean allowPrefixMatch) {
Matcher nationalNumberPatternMatcher = regexCache.getPatternForRegex(
numberDesc.getNationalNumberPattern()).matcher(nationalNumber);
return nationalNumberPatternMatcher.matches()
|| (allowPrefixMatch && nationalNumberPatternMatcher.lookingAt());
}
// @Override
public boolean matchesPossibleNumber(String nationalNumber, PhoneNumberDesc numberDesc) {
Matcher possibleNumberPatternMatcher = regexCache.getPatternForRegex(
numberDesc.getPossibleNumberPattern()).matcher(nationalNumber);
return possibleNumberPatternMatcher.matches();
}
}

+ 3
- 1
java/libphonenumber/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java View File

@ -38,9 +38,11 @@ public class CountryCodeToRegionCodeMapForTesting {
ArrayList<String> listWithRegionCode;
listWithRegionCode = new ArrayList<String>(2);
listWithRegionCode = new ArrayList<String>(4);
listWithRegionCode.add("US");
listWithRegionCode.add("BB");
listWithRegionCode.add("BS");
listWithRegionCode.add("CA");
countryCodeToRegionCodeMap.put(1, listWithRegionCode);
listWithRegionCode = new ArrayList<String>(1);


+ 12
- 9
java/libphonenumber/test/com/google/i18n/phonenumbers/ExampleNumbersTest.java View File

@ -37,7 +37,7 @@ public class ExampleNumbersTest extends TestCase {
private static final Logger LOGGER = Logger.getLogger(ExampleNumbersTest.class.getName());
private PhoneNumberUtil phoneNumberUtil =
PhoneNumberUtil.createInstance(PhoneNumberUtil.DEFAULT_METADATA_LOADER);
private ShortNumberInfo shortNumberInfo = new ShortNumberInfo(phoneNumberUtil);
private ShortNumberInfo shortNumberInfo = ShortNumberInfo.getInstance();
private List<PhoneNumber> invalidCases = new ArrayList<PhoneNumber>();
private List<PhoneNumber> wrongTypeCases = new ArrayList<PhoneNumber>();
@ -183,7 +183,8 @@ public class ExampleNumbersTest extends TestCase {
List<String> invalidStringCases = new ArrayList<String>();
for (String regionCode : shortNumberInfo.getSupportedRegions()) {
String exampleShortNumber = shortNumberInfo.getExampleShortNumber(regionCode);
if (!shortNumberInfo.isValidShortNumberForRegion(exampleShortNumber, regionCode)) {
if (!shortNumberInfo.isValidShortNumberForRegion(
phoneNumberUtil.parse(exampleShortNumber, regionCode), regionCode)) {
String invalidStringCase = "region_code: " + regionCode + ", national_number: " +
exampleShortNumber;
invalidStringCases.add(invalidStringCase);
@ -198,7 +199,8 @@ public class ExampleNumbersTest extends TestCase {
for (ShortNumberInfo.ShortNumberCost cost : ShortNumberInfo.ShortNumberCost.values()) {
exampleShortNumber = shortNumberInfo.getExampleShortNumberForCost(regionCode, cost);
if (!exampleShortNumber.equals("")) {
if (cost != shortNumberInfo.getExpectedCostForRegion(exampleShortNumber, regionCode)) {
if (cost != shortNumberInfo.getExpectedCostForRegion(
phoneNumberUtil.parse(exampleShortNumber, regionCode), regionCode)) {
wrongTypeCases.add(phoneNumber);
LOGGER.log(Level.SEVERE, "Wrong cost for " + phoneNumber.toString());
}
@ -217,12 +219,13 @@ public class ExampleNumbersTest extends TestCase {
MetadataManager.getShortNumberMetadataForRegion(regionCode).getEmergency();
if (desc.hasExampleNumber()) {
String exampleNumber = desc.getExampleNumber();
if (!exampleNumber.matches(desc.getPossibleNumberPattern()) ||
!shortNumberInfo.isEmergencyNumber(exampleNumber, regionCode)) {
PhoneNumber phoneNumber = phoneNumberUtil.parse(exampleNumber, regionCode);
if (!shortNumberInfo.isPossibleShortNumberForRegion(phoneNumber, regionCode)
|| !shortNumberInfo.isEmergencyNumber(exampleNumber, regionCode)) {
wrongTypeCounter++;
LOGGER.log(Level.SEVERE, "Emergency example number test failed for " + regionCode);
} else if (shortNumberInfo.getExpectedCostForRegion(exampleNumber, regionCode) !=
ShortNumberInfo.ShortNumberCost.TOLL_FREE) {
} else if (shortNumberInfo.getExpectedCostForRegion(phoneNumber, regionCode)
!= ShortNumberInfo.ShortNumberCost.TOLL_FREE) {
wrongTypeCounter++;
LOGGER.log(Level.WARNING, "Emergency example number not toll free for " + regionCode);
}
@ -240,8 +243,8 @@ public class ExampleNumbersTest extends TestCase {
if (desc.hasExampleNumber()) {
String exampleNumber = desc.getExampleNumber();
PhoneNumber carrierSpecificNumber = phoneNumberUtil.parse(exampleNumber, regionCode);
if (!exampleNumber.matches(desc.getPossibleNumberPattern()) ||
!shortNumberInfo.isCarrierSpecific(carrierSpecificNumber)) {
if (!shortNumberInfo.isPossibleShortNumberForRegion(carrierSpecificNumber, regionCode)
|| !shortNumberInfo.isCarrierSpecific(carrierSpecificNumber)) {
wrongTagCounter++;
LOGGER.log(Level.SEVERE, "Carrier-specific test failed for " + regionCode);
}


+ 68
- 55
java/libphonenumber/test/com/google/i18n/phonenumbers/ShortNumberInfoTest.java View File

@ -24,22 +24,18 @@ import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
* @author Shaopeng Jia
*/
public class ShortNumberInfoTest extends TestMetadataTestCase {
private ShortNumberInfo shortInfo;
public ShortNumberInfoTest() {
shortInfo = new ShortNumberInfo(phoneUtil);
}
private static final ShortNumberInfo shortInfo = ShortNumberInfo.getInstance();
public void testIsPossibleShortNumber() {
PhoneNumber possibleNumber = new PhoneNumber();
possibleNumber.setCountryCode(33).setNationalNumber(123456L);
assertTrue(shortInfo.isPossibleShortNumber(possibleNumber));
assertTrue(shortInfo.isPossibleShortNumberForRegion("123456", RegionCode.FR));
assertTrue(
shortInfo.isPossibleShortNumberForRegion(parse("123456", RegionCode.FR), RegionCode.FR));
PhoneNumber impossibleNumber = new PhoneNumber();
impossibleNumber.setCountryCode(33).setNationalNumber(9L);
assertFalse(shortInfo.isPossibleShortNumber(impossibleNumber));
assertFalse(shortInfo.isPossibleShortNumberForRegion("9", RegionCode.FR));
// Note that GB and GG share the country calling code 44, and that this number is possible but
// not valid.
@ -50,10 +46,11 @@ public class ShortNumberInfoTest extends TestMetadataTestCase {
public void testIsValidShortNumber() {
assertTrue(shortInfo.isValidShortNumber(
new PhoneNumber().setCountryCode(33).setNationalNumber(1010L)));
assertTrue(shortInfo.isValidShortNumberForRegion("1010", RegionCode.FR));
assertTrue(shortInfo.isValidShortNumberForRegion(parse("1010", RegionCode.FR), RegionCode.FR));
assertFalse(shortInfo.isValidShortNumber(
new PhoneNumber().setCountryCode(33).setNationalNumber(123456L)));
assertFalse(shortInfo.isValidShortNumberForRegion("123456", RegionCode.FR));
assertFalse(
shortInfo.isValidShortNumberForRegion(parse("123456", RegionCode.FR), RegionCode.FR));
// Note that GB and GG share the country calling code 44.
assertTrue(shortInfo.isValidShortNumber(
@ -61,44 +58,45 @@ public class ShortNumberInfoTest extends TestMetadataTestCase {
}
public void testGetExpectedCost() {
String premiumRateExample = shortInfo.getExampleShortNumberForCost(
RegionCode.FR, ShortNumberInfo.ShortNumberCost.PREMIUM_RATE);
assertEquals(ShortNumberInfo.ShortNumberCost.PREMIUM_RATE,
shortInfo.getExpectedCostForRegion(premiumRateExample, RegionCode.FR));
String premiumRateExample = shortInfo.getExampleShortNumberForCost(RegionCode.FR,
ShortNumberInfo.ShortNumberCost.PREMIUM_RATE);
assertEquals(ShortNumberInfo.ShortNumberCost.PREMIUM_RATE, shortInfo.getExpectedCostForRegion(
parse(premiumRateExample, RegionCode.FR), RegionCode.FR));
PhoneNumber premiumRateNumber = new PhoneNumber();
premiumRateNumber.setCountryCode(33).setNationalNumber(Integer.parseInt(premiumRateExample));
assertEquals(ShortNumberInfo.ShortNumberCost.PREMIUM_RATE,
shortInfo.getExpectedCost(premiumRateNumber));
String standardRateExample = shortInfo.getExampleShortNumberForCost(
RegionCode.FR, ShortNumberInfo.ShortNumberCost.STANDARD_RATE);
assertEquals(ShortNumberInfo.ShortNumberCost.STANDARD_RATE,
shortInfo.getExpectedCostForRegion(standardRateExample, RegionCode.FR));
String standardRateExample = shortInfo.getExampleShortNumberForCost(RegionCode.FR,
ShortNumberInfo.ShortNumberCost.STANDARD_RATE);
assertEquals(ShortNumberInfo.ShortNumberCost.STANDARD_RATE, shortInfo.getExpectedCostForRegion(
parse(standardRateExample, RegionCode.FR), RegionCode.FR));
PhoneNumber standardRateNumber = new PhoneNumber();
standardRateNumber.setCountryCode(33).setNationalNumber(Integer.parseInt(standardRateExample));
assertEquals(ShortNumberInfo.ShortNumberCost.STANDARD_RATE,
shortInfo.getExpectedCost(standardRateNumber));
String tollFreeExample = shortInfo.getExampleShortNumberForCost(
RegionCode.FR, ShortNumberInfo.ShortNumberCost.TOLL_FREE);
String tollFreeExample = shortInfo.getExampleShortNumberForCost(RegionCode.FR,
ShortNumberInfo.ShortNumberCost.TOLL_FREE);
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCostForRegion(tollFreeExample, RegionCode.FR));
shortInfo.getExpectedCostForRegion(parse(tollFreeExample, RegionCode.FR), RegionCode.FR));
PhoneNumber tollFreeNumber = new PhoneNumber();
tollFreeNumber.setCountryCode(33).setNationalNumber(Integer.parseInt(tollFreeExample));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCost(tollFreeNumber));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion("12345", RegionCode.FR));
shortInfo.getExpectedCostForRegion(parse("12345", RegionCode.FR), RegionCode.FR));
PhoneNumber unknownCostNumber = new PhoneNumber();
unknownCostNumber.setCountryCode(33).setNationalNumber(12345L);
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCost(unknownCostNumber));
// Test that an invalid number may nevertheless have a cost other than UNKNOWN_COST.
assertFalse(shortInfo.isValidShortNumberForRegion("116123", RegionCode.FR));
assertFalse(
shortInfo.isValidShortNumberForRegion(parse("116123", RegionCode.FR), RegionCode.FR));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCostForRegion("116123", RegionCode.FR));
shortInfo.getExpectedCostForRegion(parse("116123", RegionCode.FR), RegionCode.FR));
PhoneNumber invalidNumber = new PhoneNumber();
invalidNumber.setCountryCode(33).setNationalNumber(116123L);
assertFalse(shortInfo.isValidShortNumber(invalidNumber));
@ -107,7 +105,7 @@ public class ShortNumberInfoTest extends TestMetadataTestCase {
// Test a nonexistent country code.
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion("911", RegionCode.ZZ));
shortInfo.getExpectedCostForRegion(parse("911", RegionCode.US), RegionCode.ZZ));
unknownCostNumber.clear();
unknownCostNumber.setCountryCode(123).setNationalNumber(911L);
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
@ -119,44 +117,50 @@ public class ShortNumberInfoTest extends TestMetadataTestCase {
// code. In Australia, 1234 is premium-rate, 1194 is standard-rate, and 733 is toll-free. These
// are not known to be valid numbers in the Christmas Islands.
String ambiguousPremiumRateString = "1234";
PhoneNumber ambiguousPremiumRateNumber = new PhoneNumber().setCountryCode(61)
.setNationalNumber(1234L);
PhoneNumber ambiguousPremiumRateNumber =
new PhoneNumber().setCountryCode(61).setNationalNumber(1234L);
String ambiguousStandardRateString = "1194";
PhoneNumber ambiguousStandardRateNumber = new PhoneNumber().setCountryCode(61)
.setNationalNumber(1194L);
PhoneNumber ambiguousStandardRateNumber =
new PhoneNumber().setCountryCode(61).setNationalNumber(1194L);
String ambiguousTollFreeString = "733";
PhoneNumber ambiguousTollFreeNumber = new PhoneNumber().setCountryCode(61)
.setNationalNumber(733L);
PhoneNumber ambiguousTollFreeNumber =
new PhoneNumber().setCountryCode(61).setNationalNumber(733L);
assertTrue(shortInfo.isValidShortNumber(ambiguousPremiumRateNumber));
assertTrue(shortInfo.isValidShortNumber(ambiguousStandardRateNumber));
assertTrue(shortInfo.isValidShortNumber(ambiguousTollFreeNumber));
assertTrue(shortInfo.isValidShortNumberForRegion(ambiguousPremiumRateString, RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.PREMIUM_RATE,
shortInfo.getExpectedCostForRegion(ambiguousPremiumRateString, RegionCode.AU));
assertFalse(shortInfo.isValidShortNumberForRegion(ambiguousPremiumRateString, RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion(ambiguousPremiumRateString, RegionCode.CX));
assertTrue(shortInfo.isValidShortNumberForRegion(
parse(ambiguousPremiumRateString, RegionCode.AU), RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.PREMIUM_RATE, shortInfo.getExpectedCostForRegion(
parse(ambiguousPremiumRateString, RegionCode.AU), RegionCode.AU));
assertFalse(shortInfo.isValidShortNumberForRegion(
parse(ambiguousPremiumRateString, RegionCode.CX), RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST, shortInfo.getExpectedCostForRegion(
parse(ambiguousPremiumRateString, RegionCode.CX), RegionCode.CX));
// PREMIUM_RATE takes precedence over UNKNOWN_COST.
assertEquals(ShortNumberInfo.ShortNumberCost.PREMIUM_RATE,
shortInfo.getExpectedCost(ambiguousPremiumRateNumber));
assertTrue(shortInfo.isValidShortNumberForRegion(ambiguousStandardRateString, RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.STANDARD_RATE,
shortInfo.getExpectedCostForRegion(ambiguousStandardRateString, RegionCode.AU));
assertFalse(shortInfo.isValidShortNumberForRegion(ambiguousStandardRateString, RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion(ambiguousStandardRateString, RegionCode.CX));
assertTrue(shortInfo.isValidShortNumberForRegion(
parse(ambiguousStandardRateString, RegionCode.AU), RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.STANDARD_RATE, shortInfo.getExpectedCostForRegion(
parse(ambiguousStandardRateString, RegionCode.AU), RegionCode.AU));
assertFalse(shortInfo.isValidShortNumberForRegion(
parse(ambiguousStandardRateString, RegionCode.CX), RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST, shortInfo.getExpectedCostForRegion(
parse(ambiguousStandardRateString, RegionCode.CX), RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCost(ambiguousStandardRateNumber));
assertTrue(shortInfo.isValidShortNumberForRegion(ambiguousTollFreeString, RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCostForRegion(ambiguousTollFreeString, RegionCode.AU));
assertFalse(shortInfo.isValidShortNumberForRegion(ambiguousTollFreeString, RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion(ambiguousTollFreeString, RegionCode.CX));
assertTrue(shortInfo.isValidShortNumberForRegion(parse(ambiguousTollFreeString, RegionCode.AU),
RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE, shortInfo.getExpectedCostForRegion(
parse(ambiguousTollFreeString, RegionCode.AU), RegionCode.AU));
assertFalse(shortInfo.isValidShortNumberForRegion(parse(ambiguousTollFreeString, RegionCode.CX),
RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST, shortInfo.getExpectedCostForRegion(
parse(ambiguousTollFreeString, RegionCode.CX), RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCost(ambiguousTollFreeNumber));
}
@ -301,13 +305,13 @@ public class ShortNumberInfoTest extends TestMetadataTestCase {
public void testEmergencyNumberForSharedCountryCallingCode() {
// Test the emergency number 112, which is valid in both Australia and the Christmas Islands.
assertTrue(shortInfo.isEmergencyNumber("112", RegionCode.AU));
assertTrue(shortInfo.isValidShortNumberForRegion("112", RegionCode.AU));
assertTrue(shortInfo.isValidShortNumberForRegion(parse("112", RegionCode.AU), RegionCode.AU));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCostForRegion("112", RegionCode.AU));
shortInfo.getExpectedCostForRegion(parse("112", RegionCode.AU), RegionCode.AU));
assertTrue(shortInfo.isEmergencyNumber("112", RegionCode.CX));
assertTrue(shortInfo.isValidShortNumberForRegion("112", RegionCode.CX));
assertTrue(shortInfo.isValidShortNumberForRegion(parse("112", RegionCode.CX), RegionCode.CX));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCostForRegion("112", RegionCode.CX));
shortInfo.getExpectedCostForRegion(parse("112", RegionCode.CX), RegionCode.CX));
PhoneNumber sharedEmergencyNumber =
new PhoneNumber().setCountryCode(61).setNationalNumber(112L);
assertTrue(shortInfo.isValidShortNumber(sharedEmergencyNumber));
@ -320,12 +324,21 @@ public class ShortNumberInfoTest extends TestMetadataTestCase {
// and the USA.
assertTrue(shortInfo.isEmergencyNumber("211", RegionCode.BB));
assertEquals(ShortNumberInfo.ShortNumberCost.TOLL_FREE,
shortInfo.getExpectedCostForRegion("211", RegionCode.BB));
shortInfo.getExpectedCostForRegion(parse("211", RegionCode.BB), RegionCode.BB));
assertFalse(shortInfo.isEmergencyNumber("211", RegionCode.US));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion("211", RegionCode.US));
shortInfo.getExpectedCostForRegion(parse("211", RegionCode.US), RegionCode.US));
assertFalse(shortInfo.isEmergencyNumber("211", RegionCode.CA));
assertEquals(ShortNumberInfo.ShortNumberCost.UNKNOWN_COST,
shortInfo.getExpectedCostForRegion("211", RegionCode.CA));
shortInfo.getExpectedCostForRegion(parse("211", RegionCode.CA), RegionCode.CA));
}
private PhoneNumber parse(String number, String regionCode) {
try {
return phoneUtil.parse(number, regionCode);
} catch (NumberParseException e) {
throw new AssertionError(
"Test input data should always parse correctly: " + number + " (" + regionCode + ")", e);
}
}
}

BIN
java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_BB View File


BIN
java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_CA View File


BIN
java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_CX View File


BIN
java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_FR View File


+ 11
- 2
resources/PhoneNumberMetadataForTesting.xml View File

@ -156,6 +156,10 @@
</premiumRate>
</territory>
<!-- Barbados -->
<territory id="BB" countryCode="1" internationalPrefix="011">
</territory>
<!-- Brazil -->
<!-- This country is used to test ShortNumberInfo, so at least the country calling code must be
recognised by the library. -->
@ -220,6 +224,10 @@
</fixedLine>
</territory>
<!-- Canada -->
<territory id="CA" countryCode="1" internationalPrefix="011">
</territory>
<!-- Cocos Islands -->
<!-- Country calling code shared with Australia. -->
<!-- This country is used to test ShortNumberInfo, so at least the country calling code must be
@ -250,7 +258,7 @@
<!-- Country calling code shared with Australia. -->
<!-- This country is used to test ShortNumberInfo, so at least the country calling code must be
recognised by the library. -->
<territory id="CX" countryCode="61">
<territory id="CX" countryCode="61" internationalPrefix="00">
</territory>
<!-- Germany -->
@ -310,7 +318,8 @@
</territory>
<!-- France -->
<territory id="FR" countryCode="33" nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG">
<territory id="FR" countryCode="33" nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG"
internationalPrefix="00">
<availableFormats>
<!-- We use this to test the phone number matcher. For our test-case, the formatting
pattern must have as the first group a sub-part of the country calling code,


Loading…
Cancel
Save