Browse Source

JAVA,GEODATA: libphonenumber 3.8. Contains some bug-fixes, metadata improvements and improved phone-number-matcher behaviour. Consult the release notes for more information. Also updating a unit-test in CPP that would now fail with the testing metadata changes.

pull/567/head
Lara Scheidegger 15 years ago
committed by Mihaela Rosca
parent
commit
eedbf3ac30
128 changed files with 1896 additions and 683 deletions
  1. +2
    -2
      cpp/test/phonenumbers/phonenumberutil_test.cc
  2. +2
    -1
      java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java
  3. +23
    -0
      java/release_notes.txt
  4. +9
    -3
      java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
  5. +59
    -30
      java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java
  6. +236
    -47
      java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
  7. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AR
  8. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT
  9. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AU
  10. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AZ
  11. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BE
  12. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF
  13. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BH
  14. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CA
  15. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN
  16. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO
  17. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR
  18. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HT
  19. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HU
  20. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT
  21. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KG
  22. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KH
  23. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LB
  24. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LI
  25. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ME
  26. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NC
  27. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RS
  28. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SE
  29. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TT
  30. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US
  31. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ZA
  32. +9
    -30
      java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
  33. +14
    -60
      java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java
  34. +3
    -7
      java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java
  35. +11
    -22
      java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java
  36. +30
    -16
      java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
  37. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/1_en
  38. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/213_en
  39. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/216_en
  40. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/221_en
  41. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/224_en
  42. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/226_en
  43. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/229_en
  44. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/233_en
  45. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/261_en
  46. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/264_en
  47. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/266_en
  48. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/267_en
  49. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/27_en
  50. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/30_el
  51. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/31_nl
  52. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/33_fr
  53. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/34_es
  54. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/351_pt
  55. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/352_de
  56. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/352_en
  57. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/354_en
  58. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/355_en
  59. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/36_hu
  60. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/370_en
  61. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/371_en
  62. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/372_en
  63. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/39_it
  64. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/41_de
  65. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/41_en
  66. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/41_fr
  67. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/41_it
  68. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/420_en
  69. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/421_en
  70. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/43_de
  71. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/44_en
  72. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/46_sv
  73. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/48_pl
  74. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/49_de
  75. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/51_en
  76. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/54_es
  77. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/55_pt
  78. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/56_es
  79. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/7_en
  80. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/82_en
  81. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/82_ko
  82. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh
  83. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh_Hant
  84. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/84_en
  85. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/84_vi
  86. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/86_zh
  87. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/886_en
  88. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh
  89. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh_Hant
  90. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/90_en
  91. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/90_tr
  92. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/94_en
  93. BIN
      java/src/com/google/i18n/phonenumbers/geocoding/data/config
  94. +17
    -0
      java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java
  95. +258
    -35
      java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java
  96. +6
    -4
      java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
  97. BIN
      java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE
  98. BIN
      java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_US
  99. +8
    -9
      java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java
  100. +80
    -0
      java/test/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorageTest.java

+ 2
- 2
cpp/test/phonenumbers/phonenumberutil_test.cc View File

@ -270,7 +270,7 @@ TEST_F(PhoneNumberUtilTest, GetInstanceLoadUSMetadata) {
EXPECT_EQ("(\\d{3})(\\d{3})(\\d{4})",
metadata->number_format(1).pattern());
EXPECT_EQ("$1 $2 $3", metadata->number_format(1).format());
EXPECT_EQ("[13-9]\\d{9}|2[0-35-9]\\d{8}",
EXPECT_EQ("[13-689]\\d{9}|2[0-35-9]\\d{8}",
metadata->general_desc().national_number_pattern());
EXPECT_EQ("\\d{7}(?:\\d{3})?",
metadata->general_desc().possible_number_pattern());
@ -294,7 +294,7 @@ TEST_F(PhoneNumberUtilTest, GetInstanceLoadDEMetadata) {
EXPECT_EQ("(\\d{3})(\\d{3,4})(\\d{4})",
metadata->number_format(5).pattern());
EXPECT_EQ("$1 $2 $3", metadata->number_format(5).format());
EXPECT_EQ("(?:[24-6]\\d{2}|3[03-9]\\d|[789](?:[1-9]\\d|0[2-9]))\\d{3,8}",
EXPECT_EQ("(?:[24-6]\\d{2}|3[03-9]\\d|[789](?:[1-9]\\d|0[2-9]))\\d{1,8}",
metadata->fixed_line().national_number_pattern());
EXPECT_EQ("\\d{2,14}", metadata->fixed_line().possible_number_pattern());
EXPECT_EQ("30123456", metadata->fixed_line().example_number());


+ 2
- 1
java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java View File

@ -191,7 +191,8 @@ public class PhoneNumberParserServlet extends HttpServlet {
"Result from isValidNumberForRegion()",
Boolean.toString(phoneUtil.isValidNumberForRegion(number, defaultCountry)),
output);
appendLine("Phone Number region", phoneUtil.getRegionCodeForNumber(number), output);
String region = phoneUtil.getRegionCodeForNumber(number);
appendLine("Phone Number region", region == null ? "" : region, output);
appendLine("Result from isPossibleNumber()",
Boolean.toString(phoneUtil.isPossibleNumber(number)), output);
appendLine("Result from getNumberType()", phoneUtil.getNumberType(number).toString(), output);


+ 23
- 0
java/release_notes.txt View File

@ -1,3 +1,26 @@
August 10th, 2011: libphonenumber-3.8
* Code changes
- Fix to demo to not throw null-ptr exceptions for invalid NANPA numbers
- Fixed AYTF to not accept plus signs in the middle of input
- PhoneNumberMatcher improvements - added STRICT_GROUPING and EXACT_GROUPING
levels, numbers followed/preceded by a currency symbol will not match,
multiple numbers separated by phone-number punctuation will now match. ", "
is no longer accepted as an extension symbol when matching, only when
parsing. "x" is only accepted as a carrier code or extension marker, not
otherwise.
- Changes to handling of leading zeroes - these will not be silently ignored
anymore, but will be stored as part of the number.
- PhoneNumberOfflineGeocoder - new method to get the description of a number that assumes
the validity of the number has already been checked and will not re-verify it.
* Metadata changes
- Updates: AR, AT, AU, AZ, BE, BF, BH, CA, CN, CO, CR, HT, HU, IT, KG, KH, LB,
LI, ME, NC, RS, SE, TT, US, ZA
- New geocoding data for: AL, BF, BJ, BW, CI, CZ, DZ, EE, GH, GN, GR, HU, IS,
LK, LS, LT, LU, LV, MG, NA, PE, SK, SN, SZ, TN, VN, ZA
- Updated geocoding data for: GB, PT, US
- Revised sorting of geocoding data
July 5th, 2011
* Code changes
- Refactored AreaCodeMap to minimize binary and memory footprint by using 2 different strategies.


+ 9
- 3
java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java View File

@ -279,9 +279,9 @@ public class AsYouTypeFormatter {
if (rememberPosition) {
originalPosition = accruedInput.length();
}
// We do formatting on-the-fly only when each character entered is either a plus sign or a
// digit.
if (!PhoneNumberUtil.VALID_START_CHAR_PATTERN.matcher(Character.toString(nextChar)).matches()) {
// We do formatting on-the-fly only when each character entered is either a digit, or a plus
// sign (accepted at the start of the number only).
if (!isDigitOrLeadingPlusSign(nextChar)) {
ableToFormat = false;
}
if (!ableToFormat) {
@ -341,6 +341,12 @@ public class AsYouTypeFormatter {
}
}
private boolean isDigitOrLeadingPlusSign(char nextChar) {
return Character.isDigit(nextChar) ||
(accruedInput.length() == 1 &&
PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(Character.toString(nextChar)).matches());
}
String attemptToFormatAccruedDigits() {
for (NumberFormat numFormat : possibleFormats) {
Matcher m = regexCache.getPatternForRegex(numFormat.getPattern()).matcher(nationalNumber);


+ 59
- 30
java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java View File

@ -79,9 +79,10 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
/**
* Matches white-space, which may indicate the end of a phone number and the start of something
* else (such as a neighbouring zip-code).
* else (such as a neighbouring zip-code). If white-space is found, continues to match all
* characters that are not typically used to start a phone number.
*/
private static final Pattern GROUP_SEPARATOR = Pattern.compile("\\p{Z}+");
private static final Pattern GROUP_SEPARATOR;
/**
* Punctuation that may be at the start of a phone number - brackets and plus signs.
@ -127,14 +128,16 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
/* A digits block without punctuation. */
String digitSequence = "\\p{Nd}" + limit(1, digitBlockLimit);
String leadClass = "[" + openingParens + PhoneNumberUtil.PLUS_CHARS + "]";
String leadClassChars = openingParens + PhoneNumberUtil.PLUS_CHARS;
String leadClass = "[" + leadClassChars + "]";
LEAD_CLASS = Pattern.compile(leadClass);
GROUP_SEPARATOR = Pattern.compile("\\p{Z}" + "[^" + leadClassChars + "\\p{Nd}]*");
/* Phone number pattern allowing optional punctuation. */
PATTERN = Pattern.compile(
"(?:" + leadClass + punctuation + ")" + leadLimit +
digitSequence + "(?:" + punctuation + digitSequence + ")" + blockLimit +
"(?:" + PhoneNumberUtil.KNOWN_EXTN_PATTERNS + ")?",
"(?:" + PhoneNumberUtil.EXTN_PATTERNS_FOR_MATCHING + ")?",
PhoneNumberUtil.REGEX_FLAGS);
}
@ -178,10 +181,10 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
*
* @param util the phone number util to use
* @param text the character sequence that we will search, null for no text
* @param country the ISO 3166-1 two-letter country code indicating the country to assume for
* phone numbers not written in international format (with a leading plus, or
* with the international dialing prefix of the specified region). May be null or
* "ZZ" if only numbers with a leading plus should be considered.
* @param country the country to assume for phone numbers not written in international format
* (with a leading plus, or with the international dialing prefix of the
* specified region). May be null or "ZZ" if only numbers with a
* leading plus should be considered.
* @param leniency the leniency to use when evaluating candidate phone numbers
* @param maxTries the maximum number of invalid numbers to try before giving up on the text.
* This is to cover degenerate cases where the text has a lot of false positives
@ -290,6 +293,10 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
block.equals(UnicodeBlock.COMBINING_DIACRITICAL_MARKS);
}
private static boolean isCurrencySymbol(char character) {
return Character.getType(character) == Character.CURRENCY_SYMBOL;
}
/**
* Attempts to extract a match from a {@code candidate} character sequence.
*
@ -303,21 +310,6 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
return null;
}
// If leniency is set to VALID only, we also want to skip numbers that are surrounded by Latin
// alphabetic characters, to skip cases like abc8005001234 or 8005001234def.
if (leniency == Leniency.VALID) {
// If the candidate is not at the start of the text, and does not start with punctuation and
// the previous character is not a Latin letter, return null.
if (offset > 0 &&
(!LEAD_CLASS.matcher(candidate).lookingAt() && isLatinLetter(text.charAt(offset - 1)))) {
return null;
}
int lastCharIndex = offset + candidate.length();
if (lastCharIndex < text.length() && isLatinLetter(text.charAt(lastCharIndex))) {
return null;
}
}
// Try to come up with a valid match given the entire candidate.
String rawString = candidate.toString();
PhoneNumberMatch match = parseAndVerify(rawString, offset);
@ -344,20 +336,29 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
Matcher groupMatcher = GROUP_SEPARATOR.matcher(candidate);
if (groupMatcher.find()) {
int groupStartIndex = groupMatcher.end();
// Remove the first group.
CharSequence withoutFirstGroup = candidate.substring(groupStartIndex);
// Try the first group by itself.
CharSequence firstGroupOnly = candidate.substring(0, groupMatcher.start());
firstGroupOnly = trimAfterFirstMatch(PhoneNumberUtil.UNWANTED_END_CHAR_PATTERN,
firstGroupOnly);
PhoneNumberMatch match = parseAndVerify(firstGroupOnly.toString(), offset);
if (match != null) {
return match;
}
maxTries--;
int withoutFirstGroupStart = groupMatcher.end();
// Try the rest of the candidate without the first group.
CharSequence withoutFirstGroup = candidate.substring(withoutFirstGroupStart);
withoutFirstGroup = trimAfterFirstMatch(PhoneNumberUtil.UNWANTED_END_CHAR_PATTERN,
withoutFirstGroup);
PhoneNumberMatch match = parseAndVerify(withoutFirstGroup.toString(),
offset + groupStartIndex);
match = parseAndVerify(withoutFirstGroup.toString(), offset + withoutFirstGroupStart);
if (match != null) {
return match;
}
maxTries--;
if (maxTries > 0) {
int lastGroupStart = groupStartIndex;
int lastGroupStart = withoutFirstGroupStart;
while (groupMatcher.find()) {
// Find the last group.
lastGroupStart = groupMatcher.start();
@ -365,6 +366,12 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
CharSequence withoutLastGroup = candidate.substring(0, lastGroupStart);
withoutLastGroup = trimAfterFirstMatch(PhoneNumberUtil.UNWANTED_END_CHAR_PATTERN,
withoutLastGroup);
if (withoutLastGroup.equals(firstGroupOnly)) {
// If there are only two groups, then the group "without the last group" is the same as
// the first group. In these cases, we don't want to re-check the number group, so we exit
// already.
return null;
}
match = parseAndVerify(withoutLastGroup.toString(), offset);
if (match != null) {
return match;
@ -391,8 +398,30 @@ final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
if (!MATCHING_BRACKETS.matcher(candidate).matches()) {
return null;
}
// If leniency is set to VALID or stricter, we also want to skip numbers that are surrounded
// by Latin alphabetic characters, to skip cases like abc8005001234 or 8005001234def.
if (leniency.compareTo(Leniency.VALID) >= 0) {
// If the candidate is not at the start of the text, and does not start with phone-number
// punctuation, check the previous character.
if (offset > 0 && !LEAD_CLASS.matcher(candidate).lookingAt()) {
char previousChar = text.charAt(offset - 1);
// We return null if it is a latin letter or a currency symbol.
if (isCurrencySymbol(previousChar) || isLatinLetter(previousChar)) {
return null;
}
}
int lastCharIndex = offset + candidate.length();
if (lastCharIndex < text.length()) {
char nextChar = text.charAt(lastCharIndex);
if (isCurrencySymbol(nextChar) || isLatinLetter(nextChar)) {
return null;
}
}
}
PhoneNumber number = phoneUtil.parse(candidate, preferredRegion);
if (leniency.verify(number, phoneUtil)) {
if (leniency.verify(number, candidate, phoneUtil)) {
return new PhoneNumberMatch(offset, candidate, number);
}
} catch (NumberParseException e) {


+ 236
- 47
java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java View File

@ -202,7 +202,7 @@ public class PhoneNumberUtil {
Arrays.toString(ALPHA_MAPPINGS.keySet().toArray()).replaceAll("[, \\[\\]]", "") +
Arrays.toString(ALPHA_MAPPINGS.keySet().toArray()).toLowerCase().replaceAll("[, \\[\\]]", "");
static final String PLUS_CHARS = "+\uFF0B";
private static final Pattern PLUS_CHARS_PATTERN = Pattern.compile("[" + PLUS_CHARS + "]+");
static final Pattern PLUS_CHARS_PATTERN = Pattern.compile("[" + PLUS_CHARS + "]+");
private static final Pattern SEPARATOR_PATTERN = Pattern.compile("[" + VALID_PUNCTUATION + "]+");
private static final Pattern CAPTURING_DIGIT_PATTERN = Pattern.compile("(" + DIGITS + ")");
@ -213,7 +213,7 @@ public class PhoneNumberUtil {
// not include other punctuation, as this will be stripped later during parsing and is of no
// information value when parsing a number.
private static final String VALID_START_CHAR = "[" + PLUS_CHARS + DIGITS + "]";
static final Pattern VALID_START_CHAR_PATTERN = Pattern.compile(VALID_START_CHAR);
private static final Pattern VALID_START_CHAR_PATTERN = Pattern.compile(VALID_START_CHAR);
// Regular expression of characters typically used to start a second phone number for the purposes
// of parsing. This allows us to strip off parts of the number that are actually the start of
@ -252,35 +252,57 @@ public class PhoneNumberUtil {
// as the default extension prefix. This can be overridden by region-specific preferences.
private static final String DEFAULT_EXTN_PREFIX = " ext. ";
// Pattern to capture digits used in an extension. Places a maximum length of "7" for an
// extension.
private static final String CAPTURING_EXTN_DIGITS = "(" + DIGITS + "{1,7})";
// Regexp of all possible ways to write extensions, for use when parsing. This will be run as a
// case-insensitive regexp match. Wide character versions are also provided after each ASCII
// version. There are three regular expressions here. The first covers RFC 3966 format, where the
// extension is added using ";ext=". The second more generic one starts with optional white space
// and ends with an optional full stop (.), followed by zero or more spaces/tabs and then the
// numbers themselves. The other one covers the special case of American numbers where the
// extension is written with a hash at the end, such as "- 503#".
// Note that the only capturing groups should be around the digits that you want to capture as
// part of the extension, or else parsing will fail!
// Canonical-equivalence doesn't seem to be an option with Android java, so we allow two options
// for representing the accented o - the character itself, and one in the unicode decomposed form
// with the combining acute accent.
private static final String CAPTURING_EXTN_DIGITS = "(" + DIGITS + "{1,7})";
static final String KNOWN_EXTN_PATTERNS =
RFC3966_EXTN_PREFIX + CAPTURING_EXTN_DIGITS + "|" +
"[ \u00A0\\t,]*(?:ext(?:ensi(?:o\u0301?|\u00F3))?n?|" +
"\uFF45\uFF58\uFF54\uFF4E?|[,x\uFF58#\uFF03~\uFF5E]|int|anexo|\uFF49\uFF4E\uFF54)" +
"[:\\.\uFF0E]?[ \u00A0\\t,-]*" + CAPTURING_EXTN_DIGITS + "#?|" +
"[- ]+(" + DIGITS + "{1,5})#";
// version.
private static final String EXTN_PATTERNS_FOR_PARSING;
static final String EXTN_PATTERNS_FOR_MATCHING;
static {
// One-character symbols that can be used to indicate an extension.
String singleExtnSymbolsForMatching = "x\uFF58#\uFF03~\uFF5E";
// For parsing, we are slightly more lenient in our interpretation than for matching. Here we
// allow a "comma" as a possible extension indicator. When matching, this is hardly ever used to
// indicate this.
String singleExtnSymbolsForParsing = "," + singleExtnSymbolsForMatching;
EXTN_PATTERNS_FOR_PARSING = createExtnPattern(singleExtnSymbolsForParsing);
EXTN_PATTERNS_FOR_MATCHING = createExtnPattern(singleExtnSymbolsForMatching);
}
/**
* Helper initialiser method to create the regular-expression pattern to match extensions,
* allowing the one-char extension symbols provided by {@code singleExtnSymbols}.
*/
private static String createExtnPattern(String singleExtnSymbols) {
// There are three regular expressions here. The first covers RFC 3966 format, where the
// extension is added using ";ext=". The second more generic one starts with optional white
// space and ends with an optional full stop (.), followed by zero or more spaces/tabs and then
// the numbers themselves. The other one covers the special case of American numbers where the
// extension is written with a hash at the end, such as "- 503#".
// Note that the only capturing groups should be around the digits that you want to capture as
// part of the extension, or else parsing will fail!
// Canonical-equivalence doesn't seem to be an option with Android java, so we allow two options
// for representing the accented o - the character itself, and one in the unicode decomposed
// form with the combining acute accent.
return (RFC3966_EXTN_PREFIX + CAPTURING_EXTN_DIGITS + "|" + "[ \u00A0\\t,]*" +
"(?:ext(?:ensi(?:o\u0301?|\u00F3))?n?|\uFF45\uFF58\uFF54\uFF4E?|" +
"[" + singleExtnSymbols + "]|int|anexo|\uFF49\uFF4E\uFF54)" +
"[:\\.\uFF0E]?[ \u00A0\\t,-]*" + CAPTURING_EXTN_DIGITS + "#?|" +
"[- ]+(" + DIGITS + "{1,5})#");
}
// Regexp of all known extension prefixes used by different regions followed by 1 or more valid
// digits, for use when parsing.
private static final Pattern EXTN_PATTERN =
Pattern.compile("(?:" + KNOWN_EXTN_PATTERNS + ")$", REGEX_FLAGS);
Pattern.compile("(?:" + EXTN_PATTERNS_FOR_PARSING + ")$", REGEX_FLAGS);
// We append optionally the extension pattern to the end here, as a valid phone number may
// have an extension prefix appended, followed by 1 or more digits.
private static final Pattern VALID_PHONE_NUMBER_PATTERN =
Pattern.compile(VALID_PHONE_NUMBER + "(?:" + KNOWN_EXTN_PATTERNS + ")?", REGEX_FLAGS);
Pattern.compile(VALID_PHONE_NUMBER + "(?:" + EXTN_PATTERNS_FOR_PARSING + ")?", REGEX_FLAGS);
private static final Pattern NON_DIGITS_PATTERN = Pattern.compile("(\\D+)");
@ -375,7 +397,7 @@ public class PhoneNumberUtil {
/**
* Leniency when {@linkplain PhoneNumberUtil#findNumbers finding} potential phone numbers in text
* segments.
* segments. The levels here are ordered in increasing strictness.
*/
public enum Leniency {
/**
@ -385,7 +407,7 @@ public class PhoneNumberUtil {
*/
POSSIBLE {
@Override
boolean verify(PhoneNumber number, PhoneNumberUtil util) {
boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
return util.isPossibleNumber(number);
}
},
@ -396,13 +418,175 @@ public class PhoneNumberUtil {
*/
VALID {
@Override
boolean verify(PhoneNumber number, PhoneNumberUtil util) {
return util.isValidNumber(number);
boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
if (!util.isValidNumber(number)) {
return false;
}
return containsOnlyValidXChars(number, candidate, util);
}
},
/**
* Phone numbers accepted are {@linkplain PhoneNumberUtil#isValidNumber(PhoneNumber) valid} and
* are grouped in a possible way for this locale. For example, a US number written as
* "65 02 53 00 00" and "650253 0000" are not accepted at this leniency level, whereas
* "650 253 0000", "650 2530000" or "6502530000" are.
* Numbers with more than one '/' symbol are also dropped at this level.
* <p>
* Warning: This level might result in lower coverage especially for regions outside of country
* code "+1". If you are not sure about which level to use, email the discussion group
* libphonenumber-discuss@googlegroups.com.
*/
STRICT_GROUPING {
@Override
boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
if (!util.isValidNumber(number) ||
!containsOnlyValidXChars(number, candidate, util) ||
containsMoreThanOneSlash(candidate)) {
return false;
}
// TODO: Evaluate how this works for other locales (testing has been
// limited to NANPA regions) and optimise if necessary.
String[] formattedNumberGroups = getNationalNumberGroups(util, number);
StringBuilder normalizedCandidate = normalizeDigits(candidate,
true /* keep strip non-digits */);
int fromIndex = 0;
// Check each group of consecutive digits are not broken into separate groups in the
// {@code candidate} string.
for (int i = 0; i < formattedNumberGroups.length; i++) {
// Fails if the substring of {@code candidate} starting from {@code fromIndex} doesn't
// contain the consecutive digits in formattedNumberGroups[i].
fromIndex = normalizedCandidate.indexOf(formattedNumberGroups[i], fromIndex);
if (fromIndex < 0) {
return false;
}
// Moves {@code fromIndex} forward.
fromIndex += formattedNumberGroups[i].length();
if (i == 0 && fromIndex < normalizedCandidate.length()) {
// We are at the position right after the NDC.
if (Character.isDigit(normalizedCandidate.charAt(fromIndex))) {
// This means there is no formatting symbol after the NDC. In this case, we only
// accept the number if there is no formatting symbol at all in the number, except
// for extensions.
String nationalSignificantNumber = util.getNationalSignificantNumber(number);
return normalizedCandidate.substring(fromIndex - formattedNumberGroups[i].length())
.startsWith(nationalSignificantNumber);
}
}
}
// The check here makes sure that we haven't mistakenly already used the extension to
// match the last group of the subscriber number. Note the extension cannot have
// formatting in-between digits.
return normalizedCandidate.substring(fromIndex).contains(number.getExtension());
}
},
/**
* Phone numbers accepted are {@linkplain PhoneNumberUtil#isValidNumber(PhoneNumber) valid} and
* are grouped in the same way that we would have formatted it, or as a single block. For
* example, a US number written as "650 2530000" is not accepted at this leniency level, whereas
* "650 253 0000" or "6502530000" are.
* Numbers with more than one '/' symbol are also dropped at this level.
* <p>
* Warning: This level might result in lower coverage especially for regions outside of country
* code "+1". If you are not sure about which level to use, email the discussion group
* libphonenumber-discuss@googlegroups.com.
*/
EXACT_GROUPING {
@Override
boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
if (!util.isValidNumber(number) ||
!containsOnlyValidXChars(number, candidate, util) ||
containsMoreThanOneSlash(candidate)) {
return false;
}
// TODO: Evaluate how this works for other locales (testing has been
// limited to NANPA regions) and optimise if necessary.
StringBuilder normalizedCandidate = normalizeDigits(candidate,
true /* keep strip non-digits */);
String[] candidateGroups =
NON_DIGITS_PATTERN.split(normalizedCandidate.toString());
// Set this to the last group, skipping it if the number has an extension.
int candidateNumberGroupIndex =
number.hasExtension() ? candidateGroups.length - 2 : candidateGroups.length - 1;
// First we check if the national significant number is formatted as a block.
// We use contains and not equals, since the national significant number may be present with
// a prefix such as a national number prefix, or the country code itself.
if (candidateGroups.length == 1 ||
candidateGroups[candidateNumberGroupIndex].contains(
util.getNationalSignificantNumber(number))) {
return true;
}
String[] formattedNumberGroups = getNationalNumberGroups(util, number);
// Starting from the end, go through in reverse, excluding the first group, and check the
// candidate and number groups are the same.
for (int formattedNumberGroupIndex = (formattedNumberGroups.length - 1);
formattedNumberGroupIndex > 0 && candidateNumberGroupIndex >= 0;
formattedNumberGroupIndex--, candidateNumberGroupIndex--) {
if (!candidateGroups[candidateNumberGroupIndex].equals(
formattedNumberGroups[formattedNumberGroupIndex])) {
return false;
}
}
// Now check the first group. There may be a national prefix at the start, so we only check
// that the candidate group ends with the formatted number group.
return (candidateNumberGroupIndex >= 0 &&
candidateGroups[candidateNumberGroupIndex].endsWith(formattedNumberGroups[0]));
}
};
/**
* Helper method to get the national-number part of a number, formatted without any national
* prefix, and return it as a set of digit blocks that would be formatted together.
*/
private static String[] getNationalNumberGroups(PhoneNumberUtil util, PhoneNumber number) {
// This will be in the format +CC-DG;ext=EXT where DG represents groups of digits.
String rfc3966Format = util.format(number, PhoneNumberFormat.RFC3966);
// We remove the extension part from the formatted string before splitting it into different
// groups.
int endIndex = rfc3966Format.indexOf(';');
if (endIndex < 0) {
endIndex = rfc3966Format.length();
}
// The country-code will have a '-' following it.
int startIndex = rfc3966Format.indexOf('-') + 1;
return rfc3966Format.substring(startIndex, endIndex).split("-");
}
private static boolean containsMoreThanOneSlash(String candidate) {
int firstSlashIndex = candidate.indexOf('/');
return (firstSlashIndex > 0 && candidate.substring(firstSlashIndex + 1).contains("/"));
}
private static boolean containsOnlyValidXChars(
PhoneNumber number, String candidate, PhoneNumberUtil util) {
// The characters 'x' and 'X' can be (1) a carrier code, in which case they always precede the
// national significant number or (2) an extension sign, in which case they always precede the
// extension number. We assume a carrier code is more than 1 digit, so the first case has to
// have more than 1 consecutive 'x' or 'X', whereas the second case can only have exactly 1
// 'x' or 'X'. We ignore the character if it appears as the last character of the string.
for (int index = 0; index < candidate.length() - 1; index++) {
char charAtIndex = candidate.charAt(index);
if (charAtIndex == 'x' || charAtIndex == 'X') {
char charAtNextIndex = candidate.charAt(index + 1);
if (charAtNextIndex == 'x' || charAtNextIndex == 'X') {
// This is the carrier code case, in which the 'X's always precede the national
// significant number.
index++;
if (util.isNumberMatch(number, candidate.substring(index)) != MatchType.NSN_MATCH) {
return false;
}
// This is the extension sign case, in which the 'x' or 'X' should always precede the
// extension number.
} else if (!PhoneNumberUtil.normalizeDigitsOnly(candidate.substring(index)).equals(
number.getExtension())) {
return false;
}
}
}
return true;
}
/** Returns true if {@code number} is a verified number according to this leniency. */
abstract boolean verify(PhoneNumber number, PhoneNumberUtil util);
abstract boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util);
}
/**
@ -534,15 +718,20 @@ public class PhoneNumberUtil {
* @return the normalized string version of the phone number
*/
public static String normalizeDigitsOnly(String number) {
int numberLength = number.length();
StringBuilder normalizedDigits = new StringBuilder(numberLength);
for (int i = 0; i < numberLength; i++) {
int d = Character.digit(number.charAt(i), 10);
if (d != -1) {
normalizedDigits.append(d);
return normalizeDigits(number, false /* strip non-digits */).toString();
}
private static StringBuilder normalizeDigits(String number, boolean keepNonDigits) {
StringBuilder normalizedDigits = new StringBuilder(number.length());
for (char c : number.toCharArray()) {
int digit = Character.digit(c, 10);
if (digit != -1) {
normalizedDigits.append(digit);
} else if (keepNonDigits) {
normalizedDigits.append(c);
}
}
return normalizedDigits.toString();
return normalizedDigits;
}
/**
@ -1603,7 +1792,7 @@ public class PhoneNumberUtil {
* could contain a leading zero. An example of such a region is Italy. Returns false if no
* metadata for the country is found.
*/
public boolean isLeadingZeroPossible(int countryCallingCode) {
boolean isLeadingZeroPossible(int countryCallingCode) {
PhoneMetadata mainMetadataForCallingCode = getMetadataForRegion(
getRegionCodeForCountryCode(countryCallingCode));
if (mainMetadataForCallingCode == null) {
@ -1972,13 +2161,15 @@ public class PhoneNumberUtil {
String possibleNationalPrefix = metadata.getNationalPrefixForParsing();
if (numberLength == 0 || possibleNationalPrefix.length() == 0) {
// Early return for numbers of zero length.
return carrierCode;
return "";
}
// Attempt to parse the first digits as a national prefix.
Matcher prefixMatcher = regexCache.getPatternForRegex(possibleNationalPrefix).matcher(number);
if (prefixMatcher.lookingAt()) {
Pattern nationalNumberRule =
regexCache.getPatternForRegex(metadata.getGeneralDesc().getNationalNumberPattern());
// Check if the original number is viable.
boolean isViableOriginalNumber = nationalNumberRule.matcher(number).matches();
// prefixMatcher.group(numOfGroups) == null implies nothing was captured by the capturing
// groups in possibleNationalPrefix; therefore, no transformation is necessary, and we just
// remove the national prefix.
@ -1986,23 +2177,23 @@ public class PhoneNumberUtil {
String transformRule = metadata.getNationalPrefixTransformRule();
if (transformRule == null || transformRule.length() == 0 ||
prefixMatcher.group(numOfGroups) == null) {
// Check that the resultant number is viable. If not, return.
Matcher nationalNumber = nationalNumberRule.matcher(number.substring(prefixMatcher.end()));
if (!nationalNumber.matches()) {
return carrierCode;
// If the original number was viable, and the resultant number is not, we return.
if (isViableOriginalNumber &&
!nationalNumberRule.matcher(number.substring(prefixMatcher.end())).matches()) {
return "";
}
if (numOfGroups > 0 && prefixMatcher.group(numOfGroups) != null) {
carrierCode = prefixMatcher.group(1);
}
number.delete(0, prefixMatcher.end());
} else {
// Check that the resultant number is viable. If not, return. Check this by copying the
// string buffer and making the transformation on the copy first.
// Check that the resultant number is still viable. If not, return. Check this by copying
// the string buffer and making the transformation on the copy first.
StringBuilder transformedNumber = new StringBuilder(number);
transformedNumber.replace(0, numberLength, prefixMatcher.replaceFirst(transformRule));
Matcher nationalNumber = nationalNumberRule.matcher(transformedNumber.toString());
if (!nationalNumber.matches()) {
return carrierCode;
if (isViableOriginalNumber &&
!nationalNumberRule.matcher(transformedNumber.toString()).matches()) {
return "";
}
if (numOfGroups > 1) {
carrierCode = prefixMatcher.group(1);
@ -2246,9 +2437,7 @@ public class PhoneNumberUtil {
throw new NumberParseException(NumberParseException.ErrorType.TOO_LONG,
"The string supplied is too long to be a phone number.");
}
if (normalizedNationalNumber.charAt(0) == '0' &&
regionMetadata != null &&
regionMetadata.isLeadingZeroPossible()) {
if (normalizedNationalNumber.charAt(0) == '0') {
phoneNumber.setItalianLeadingZero(true);
}
phoneNumber.setNationalNumber(Long.parseLong(normalizedNationalNumber.toString()));


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AR View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AU View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AZ View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BE View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BH View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CA View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HT View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HU View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KG View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KH View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LB View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LI View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ME View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NC View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RS View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SE View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TT View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US View File


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ZA View File


+ 9
- 30
java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java View File

@ -36,8 +36,6 @@ import java.util.logging.Logger;
* @author Shaopeng Jia
*/
public class AreaCodeMap implements Externalizable {
private final int countryCallingCode;
private final boolean isLeadingZeroPossible;
private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
private static final Logger LOGGER = Logger.getLogger(AreaCodeMap.class.getName());
@ -50,16 +48,10 @@ public class AreaCodeMap implements Externalizable {
/**
* Creates an empty {@link AreaCodeMap}. The default constructor is necessary for implementing
* {@link Externalizable}. The empty map could later populated by
* {@link Externalizable}. The empty map could later be populated by
* {@link #readAreaCodeMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}.
*
* @param countryCallingCode the country calling code for the region that the area code map
* belongs to.
*/
public AreaCodeMap(int countryCallingCode) {
this.countryCallingCode = countryCallingCode;
isLeadingZeroPossible = phoneUtil.isLeadingZeroPossible(countryCallingCode);
}
public AreaCodeMap() {}
/**
* Gets the size of the provided area code map storage. The map storage passed-in will be filled
@ -78,11 +70,11 @@ public class AreaCodeMap implements Externalizable {
}
private AreaCodeMapStorageStrategy createDefaultMapStorage() {
return new DefaultMapStorage(countryCallingCode, isLeadingZeroPossible);
return new DefaultMapStorage();
}
private AreaCodeMapStorageStrategy createFlyweightMapStorage() {
return new FlyweightMapStorage(countryCallingCode, isLeadingZeroPossible);
return new FlyweightMapStorage();
}
/**
@ -126,9 +118,9 @@ public class AreaCodeMap implements Externalizable {
// Read the area code map storage strategy flag.
boolean useFlyweightMapStorage = objectInput.readBoolean();
if (useFlyweightMapStorage) {
areaCodeMapStorage = new FlyweightMapStorage(countryCallingCode, isLeadingZeroPossible);
areaCodeMapStorage = new FlyweightMapStorage();
} else {
areaCodeMapStorage = new DefaultMapStorage(countryCallingCode, isLeadingZeroPossible);
areaCodeMapStorage = new DefaultMapStorage();
}
areaCodeMapStorage.readExternal(objectInput);
}
@ -152,9 +144,8 @@ public class AreaCodeMap implements Externalizable {
if (numOfEntries == 0) {
return "";
}
long phonePrefix = isLeadingZeroPossible
? Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number))
: Long.parseLong(phoneUtil.getNationalSignificantNumber(number));
long phonePrefix =
Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
int currentIndex = numOfEntries - 1;
SortedSet<Integer> currentSetOfLengths = areaCodeMapStorage.getPossibleLengths();
while (currentSetOfLengths.size() > 0) {
@ -204,18 +195,6 @@ public class AreaCodeMap implements Externalizable {
*/
@Override
public String toString() {
StringBuilder output = new StringBuilder();
int numOfEntries = areaCodeMapStorage.getNumOfEntries();
for (int i = 0; i < numOfEntries; i++) {
if (!isLeadingZeroPossible) {
output.append(countryCallingCode);
}
output.append(areaCodeMapStorage.getPrefix(i));
output.append("|");
output.append(areaCodeMapStorage.getDescription(i));
output.append("\n");
}
return output.toString();
return areaCodeMapStorage.toString();
}
}

+ 14
- 60
java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java View File

@ -23,29 +23,18 @@ import java.util.SortedMap;
import java.util.TreeSet;
/**
* Abstracts the way area code data is stored into memory and serialized to a stream.
* Abstracts the way area code data is stored into memory and serialized to a stream. It is used by
* {@link AreaCodeMap} to support the most space-efficient storage strategy according to the
* provided data.
*
* @author Philippe Liard
*/
// @VisibleForTesting
abstract class AreaCodeMapStorageStrategy {
protected final int countryCallingCode;
protected final boolean isLeadingZeroPossible;
protected int numOfEntries = 0;
protected final TreeSet<Integer> possibleLengths = new TreeSet<Integer>();
/**
* Constructs a new area code map storage strategy from the provided country calling code and
* boolean parameter.
*
* @param countryCallingCode the country calling code of the number prefixes contained in the map
* @param isLeadingZeroPossible whether the phone number prefixes belong to a region which
* {@link com.google.i18n.phonenumbers.PhoneNumberUtil#isLeadingZeroPossible}
*/
public AreaCodeMapStorageStrategy(int countryCallingCode, boolean isLeadingZeroPossible) {
this.countryCallingCode = countryCallingCode;
this.isLeadingZeroPossible = isLeadingZeroPossible;
}
public AreaCodeMapStorageStrategy() {}
/**
* Returns whether the underlying implementation of this abstract class is flyweight.
@ -113,52 +102,17 @@ abstract class AreaCodeMapStorageStrategy {
*/
public abstract void writeExternal(ObjectOutput objectOutput) throws IOException;
/**
* Utility class used to pass arguments by "reference".
*/
protected static class Reference<T> {
private T data;
T get () {
return data;
}
void set (T data) {
this.data = data;
}
}
@Override
public String toString() {
StringBuilder output = new StringBuilder();
int numOfEntries = getNumOfEntries();
/**
* Removes the country calling code from the provided {@code prefix} if the country can't have any
* leading zero; otherwise it is left as it is. Sets the provided {@code lengthOfPrefixRef}
* parameter to the length of the resulting prefix.
*
* @param prefix a phone number prefix containing a leading country calling code
* @param lengthOfPrefixRef a "reference" to an integer set to the length of the resulting
* prefix. This parameter is ignored when set to null.
* @return the resulting prefix which may have been stripped
*/
protected int stripPrefix(int prefix, Reference<Integer> lengthOfPrefixRef) {
int lengthOfCountryCode = (int) Math.log10(countryCallingCode) + 1;
int lengthOfPrefix = (int) Math.log10(prefix) + 1;
if (!isLeadingZeroPossible) {
lengthOfPrefix -= lengthOfCountryCode;
prefix -= countryCallingCode * (int) Math.pow(10, lengthOfPrefix);
}
if (lengthOfPrefixRef != null) {
lengthOfPrefixRef.set(lengthOfPrefix);
for (int i = 0; i < numOfEntries; i++) {
output.append(getPrefix(i));
output.append("|");
output.append(getDescription(i));
output.append("\n");
}
return prefix;
}
/**
* Removes the country calling code from the provided {@code prefix} if the country can't have any
* leading zero; otherwise it is left as it is.
*
* @param prefix a phone number prefix containing a leading country calling code
* @return the resulting prefix which may have been stripped
*/
protected int stripPrefix(int prefix) {
return stripPrefix(prefix, null);
return output.toString();
}
}

+ 3
- 7
java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java View File

@ -30,9 +30,7 @@ import java.util.SortedMap;
*/
class DefaultMapStorage extends AreaCodeMapStorageStrategy {
public DefaultMapStorage(int countryCallingCode, boolean isLeadingZeroPossible) {
super(countryCallingCode, isLeadingZeroPossible);
}
public DefaultMapStorage() {}
private int[] phoneNumberPrefixes;
private String[] descriptions;
@ -59,10 +57,8 @@ class DefaultMapStorage extends AreaCodeMapStorageStrategy {
descriptions = new String[numOfEntries];
int index = 0;
for (int prefix : sortedAreaCodeMap.keySet()) {
Reference<Integer> lengthOfPrefixRef = new Reference<Integer>();
int strippedPrefix = stripPrefix(prefix, lengthOfPrefixRef);
phoneNumberPrefixes[index++] = strippedPrefix;
possibleLengths.add(lengthOfPrefixRef.get());
phoneNumberPrefixes[index++] = prefix;
possibleLengths.add((int) Math.log10(prefix) + 1);
}
sortedAreaCodeMap.values().toArray(descriptions);
}


+ 11
- 22
java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java View File

@ -22,8 +22,6 @@ import java.io.ObjectOutput;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.SortedSet;
@ -32,7 +30,7 @@ import java.util.TreeSet;
/**
* Flyweight area code map storage strategy that uses a table to store unique strings and shorts to
* store the prefix and description indexes when possible. It is particularly space-efficient when
* the provided area code map contains a lot of description duplicates.
* the provided area code map contains a lot of redundant descriptions.
*
* @author Philippe Liard
*/
@ -47,17 +45,13 @@ class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
// description pool containing all the strings.
private int descIndexSizeInBytes;
// Byte buffer of stripped phone number prefixes. A stripped phone number prefix is a phone number
// prefix omitting the country code.
private ByteBuffer phoneNumberPrefixes;
private ByteBuffer descriptionIndexes;
// Sorted string array of unique description strings.
private String[] descriptionPool;
public FlyweightMapStorage(int countryCallingCode, boolean isLeadingZeroPossible) {
super(countryCallingCode, isLeadingZeroPossible);
}
public FlyweightMapStorage() {}
@Override
public boolean isFlyweight() {
@ -78,7 +72,7 @@ class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
*
* @param buffer the byte buffer to which the value is stored
* @param wordSize the number of bytes used to store the provided value
* @param index the index in bytes to which the value is stored
* @param index the index to which the value is stored
* @param value the value that is stored assuming it does not require more than the specified
* number of bytes.
*/
@ -98,7 +92,7 @@ class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
*
* @param buffer the byte buffer from which the value is read
* @param wordSize the number of bytes used to store the value
* @param index the index in bytes where the value is read from
* @param index the index where the value is read from
*
* @return the value read from the buffer
*/
@ -121,20 +115,16 @@ class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
public void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap) {
SortedSet<String> descriptionsSet = new TreeSet<String>();
numOfEntries = sortedAreaCodeMap.size();
prefixSizeInBytes = getOptimalNumberOfBytesForValue(stripPrefix(sortedAreaCodeMap.lastKey()));
prefixSizeInBytes = getOptimalNumberOfBytesForValue(sortedAreaCodeMap.lastKey());
phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
Map<Integer, Integer> strippedToUnstrippedPrefixes = new HashMap<Integer, Integer>();
// Fill the phone number prefixes byte buffer, the set of possible lengths of prefixes and the
// description set.
int index = 0;
for (Entry<Integer, String> entry : sortedAreaCodeMap.entrySet()) {
int prefix = entry.getKey();
Reference<Integer> lengthOfPrefixRef = new Reference<Integer>();
int strippedPrefix = stripPrefix(prefix, lengthOfPrefixRef);
strippedToUnstrippedPrefixes.put(strippedPrefix, prefix);
storeWordInBuffer(phoneNumberPrefixes, prefixSizeInBytes, index++, strippedPrefix);
possibleLengths.add(lengthOfPrefixRef.get());
storeWordInBuffer(phoneNumberPrefixes, prefixSizeInBytes, index++, prefix);
possibleLengths.add((int) Math.log10(prefix) + 1);
descriptionsSet.add(entry.getValue());
}
@ -147,15 +137,14 @@ class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
// Map the phone number prefixes to the descriptions.
index = 0;
for (int i = 0; i < numOfEntries; i++) {
int strippedPrefix = readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, i);
int prefix = strippedToUnstrippedPrefixes.get(strippedPrefix);
int prefix = readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, i);
String description = sortedAreaCodeMap.get(prefix);
int positionIndescriptionPool =
int positionInDescriptionPool =
Arrays.binarySearch(descriptionPool, description, new Comparator<String>() {
public int compare(String o1, String o2) { return o1.compareTo(o2); }
});
storeWordInBuffer(descriptionIndexes, descIndexSizeInBytes, index++,
positionIndescriptionPool);
positionInDescriptionPool);
}
}
@ -166,7 +155,7 @@ class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
* @param objectInput the object input stream from which the value is read
* @param wordSize the number of bytes used to store the value read from the stream
* @param outputBuffer the byte buffer to which the value is stored
* @param index the index in bytes where the value is stored in the buffer
* @param index the index where the value is stored in the buffer
* @throws IOException if an error occurred reading from the object input stream
*/
private static void readExternalWord(ObjectInput objectInput, int wordSize,


+ 30
- 16
java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java View File

@ -50,11 +50,7 @@ public class PhoneNumberOfflineGeocoder {
// loaded.
private Map<String, AreaCodeMap> availablePhonePrefixMaps = new HashMap<String, AreaCodeMap>();
/**
* For testing purposes, we allow the phone number util variable to be injected.
*
* @VisibleForTesting
*/
// @VisibleForTesting
PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
this.phonePrefixDataDirectory = phonePrefixDataDirectory;
loadMappingFileProvider();
@ -79,18 +75,18 @@ public class PhoneNumberOfflineGeocoder {
return null;
}
if (!availablePhonePrefixMaps.containsKey(fileName)) {
loadAreaCodeMapFromFile(fileName, countryCallingCode);
loadAreaCodeMapFromFile(fileName);
}
return availablePhonePrefixMaps.get(fileName);
}
private void loadAreaCodeMapFromFile(String fileName, int countryCallingCode) {
private void loadAreaCodeMapFromFile(String fileName) {
InputStream source =
PhoneNumberOfflineGeocoder.class.getResourceAsStream(phonePrefixDataDirectory + fileName);
ObjectInputStream in;
try {
in = new ObjectInputStream(source);
AreaCodeMap map = new AreaCodeMap(countryCallingCode);
AreaCodeMap map = new AreaCodeMap();
map.readExternal(in);
availablePhonePrefixMaps.put(fileName, map);
} catch (IOException e) {
@ -140,22 +136,40 @@ public class PhoneNumberOfflineGeocoder {
/**
* Returns a text description for the given language code for the given phone number. The
* description might consist of the name of the country where the phone number is from and/or the
* name of the geographical area the phone number is from.
* name of the geographical area the phone number is from. This method assumes the validity of the
* number passed in has already been checked.
*
* @param number the phone number for which we want to get a text description
* @param number a valid phone number for which we want to get a text description
* @param languageCode the language code for which the description should be written
* @return a text description for the given language code for the given phone number
*/
public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
String langStr = languageCode.getLanguage();
String scriptStr = ""; // No script is specified
String regionStr = languageCode.getCountry();
String areaDescription =
getAreaDescriptionForNumber(number, langStr, scriptStr, regionStr);
return (areaDescription.length() > 0)
? areaDescription : getCountryNameForNumber(number, languageCode);
}
/**
* Returns a text description for the given language code for the given phone number. The
* description might consist of the name of the country where the phone number is from and/or the
* name of the geographical area the phone number is from. This method explictly checkes the
* validity of the number passed in.
*
* @param number the phone number for which we want to get a text description
* @param languageCode the language code for which the description should be written
* @return a text description for the given language code for the given phone number, or empty
* string if the number passed in is invalid
*/
public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
if (!phoneUtil.isValidNumber(number)) {
return "";
}
String areaDescription =
getAreaDescriptionForNumber(
number, languageCode.getLanguage(), "", // No script is specified.
languageCode.getCountry());
return (areaDescription.length() > 0)
? areaDescription : getCountryNameForNumber(number, languageCode);
return getDescriptionForValidNumber(number, languageCode);
}
/**


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/1_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/213_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/216_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/221_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/224_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/226_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/229_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/233_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/261_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/264_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/266_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/267_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/27_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/30_el View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/31_nl View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/33_fr View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/34_es View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/351_pt View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/352_de View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/352_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/354_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/355_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/36_hu View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/370_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/371_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/372_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/39_it View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/41_de View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/41_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/41_fr View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/41_it View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/420_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/421_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/43_de View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/44_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/46_sv View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/48_pl View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/49_de View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/51_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/54_es View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/55_pt View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/56_es View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/7_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/82_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/82_ko View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh_Hant View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/84_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/84_vi View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/86_zh View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/886_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh_Hant View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/90_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/90_tr View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/94_en View File


BIN
java/src/com/google/i18n/phonenumbers/geocoding/data/config View File


+ 17
- 0
java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java View File

@ -56,6 +56,23 @@ public class AsYouTypeFormatterTest extends TestCase {
assertEquals("650253", formatter.inputDigit('3'));
}
public void testInvalidPlusSign() {
AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("ZZ");
assertEquals("+", formatter.inputDigit('+'));
assertEquals("+4", formatter.inputDigit('4'));
assertEquals("+48 ", formatter.inputDigit('8'));
assertEquals("+48 8", formatter.inputDigit('8'));
assertEquals("+48 88", formatter.inputDigit('8'));
assertEquals("+48 88 1", formatter.inputDigit('1'));
assertEquals("+48 88 12", formatter.inputDigit('2'));
assertEquals("+48 88 123", formatter.inputDigit('3'));
assertEquals("+48 88 123 1", formatter.inputDigit('1'));
// A plus sign can only appear at the beginning of the number; otherwise, no formatting is
// applied.
assertEquals("+48881231+", formatter.inputDigit('+'));
assertEquals("+48881231+2", formatter.inputDigit('2'));
}
public void testTooLongNumberMatchingMultipleLeadingDigits() {
// See http://code.google.com/p/libphonenumber/issues/detail?id=36
// The bug occurred last time for countries which have two formatting rules with exactly the


+ 258
- 35
java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java View File

@ -22,6 +22,7 @@ import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@ -115,6 +116,8 @@ public class PhoneNumberMatcherTest extends TestCase {
/** See {@link PhoneNumberUtilTest#testParseWithXInNumber()}. */
public void testFindWithXInNumber() throws Exception {
doTestFindInContext("(0xx) 123456789", "AR");
// A case where x denotes both carrier codes and extension symbol.
doTestFindInContext("(0xx) 123456789 x 1234", "AR");
// This test is intentionally constructed such that the number of digit after xx is larger than
// 7, so that the number won't be mistakenly treated as an extension, as we allow extensions up
@ -168,7 +171,9 @@ public class PhoneNumberMatcherTest extends TestCase {
doTestFindInContext("(800) 901-3355 x 7246433", "US");
doTestFindInContext("(800) 901-3355 , ext 7246433", "US");
doTestFindInContext("(800) 901-3355 ,extension 7246433", "US");
doTestFindInContext("(800) 901-3355 , 7246433", "US");
// The next test differs from PhoneNumberUtil -> when matching we don't consider a lone comma to
// indicate an extension, although we accept it when parsing.
doTestFindInContext("(800) 901-3355 ,x 7246433", "US");
doTestFindInContext("(800) 901-3355 ext: 7246433", "US");
}
@ -199,10 +204,11 @@ public class PhoneNumberMatcherTest extends TestCase {
public void testMatchWithSurroundingZipcodes() throws Exception {
String number = "415-666-7777";
String zipPreceding = "My address is CA 34215. " + number + " is my number.";
String zipPreceding = "My address is CA 34215 - " + number + " is my number.";
PhoneNumber expectedResult = phoneUtil.parse(number, "US");
Iterator<PhoneNumberMatch> iterator = phoneUtil.findNumbers(zipPreceding, "US").iterator();
Iterator<PhoneNumberMatch> iterator =
phoneUtil.findNumbers(zipPreceding, "US").iterator();
PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null;
assertNotNull("Did not find a number in '" + zipPreceding + "'; expected " + number, match);
assertEquals(expectedResult, match.number());
@ -236,24 +242,42 @@ public class PhoneNumberMatcherTest extends TestCase {
}
public void testMatchesWithSurroundingLatinChars() throws Exception {
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(5);
contextPairs.add(new NumberContext("abc", "def"));
contextPairs.add(new NumberContext("abc", ""));
contextPairs.add(new NumberContext("", "def"));
// Latin small letter e with an acute accent.
contextPairs.add(new NumberContext("\u00C9", ""));
// Same character decomposed (with combining mark).
contextPairs.add(new NumberContext("e\u0301", ""));
ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>();
possibleOnlyContexts.add(new NumberContext("abc", "def"));
possibleOnlyContexts.add(new NumberContext("abc", ""));
possibleOnlyContexts.add(new NumberContext("", "def"));
// Latin capital letter e with an acute accent.
possibleOnlyContexts.add(new NumberContext("\u00C9", ""));
// e with an acute accent decomposed (with combining mark).
possibleOnlyContexts.add(new NumberContext("e\u0301", ""));
// Numbers should not be considered valid, if they are surrounded by Latin characters, but
// should be considered possible.
findMatchesInContexts(contextPairs, false, true);
findMatchesInContexts(possibleOnlyContexts, false, true);
}
public void testMoneyNotSeenAsPhoneNumber() throws Exception {
ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>();
possibleOnlyContexts.add(new NumberContext("$", ""));
possibleOnlyContexts.add(new NumberContext("", "$"));
possibleOnlyContexts.add(new NumberContext("\u00A3", "")); // Pound sign
possibleOnlyContexts.add(new NumberContext("\u00A5", "")); // Yen sign
findMatchesInContexts(possibleOnlyContexts, false, true);
}
public void testPhoneNumberWithLeadingOrTrailingMoneyMatches() throws Exception {
// Because of the space after the 20 (or before the 100) these dollar amounts should not stop
// the actual number from being found.
ArrayList<NumberContext> contexts = new ArrayList<NumberContext>();
contexts.add(new NumberContext("$20 ", ""));
contexts.add(new NumberContext("", " 100$"));
findMatchesInContexts(contexts, true, true);
}
public void testMatchesWithSurroundingLatinCharsAndLeadingPunctuation() throws Exception {
// Contexts with trailing characters. Leading characters are okay here since the numbers we will
// insert start with punctuation, but trailing characters are still not allowed.
ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>(3);
ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>();
possibleOnlyContexts.add(new NumberContext("abc", "def"));
possibleOnlyContexts.add(new NumberContext("", "def"));
possibleOnlyContexts.add(new NumberContext("", "\u00C9"));
@ -265,7 +289,7 @@ public class PhoneNumberMatcherTest extends TestCase {
findMatchesInContexts(possibleOnlyContexts, false, true, "US", numberWithPlus);
findMatchesInContexts(possibleOnlyContexts, false, true, "US", numberWithBrackets);
ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(4);
ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>();
validContexts.add(new NumberContext("abc", ""));
validContexts.add(new NumberContext("\u00C9", ""));
validContexts.add(new NumberContext("\u00C9", ".")); // Trailing punctuation.
@ -277,7 +301,7 @@ public class PhoneNumberMatcherTest extends TestCase {
}
public void testMatchesWithSurroundingChineseChars() throws Exception {
ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(3);
ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>();
validContexts.add(new NumberContext("\u6211\u7684\u7535\u8BDD\u53F7\u7801\u662F", ""));
validContexts.add(new NumberContext("", "\u662F\u6211\u7684\u7535\u8BDD\u53F7\u7801"));
validContexts.add(new NumberContext("\u8BF7\u62E8\u6253", "\u6211\u5728\u660E\u5929"));
@ -287,7 +311,7 @@ public class PhoneNumberMatcherTest extends TestCase {
}
public void testMatchesWithSurroundingPunctuation() throws Exception {
ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(4);
ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>();
validContexts.add(new NumberContext("My number-", "")); // At end of text.
validContexts.add(new NumberContext("", ".Nice day.")); // At start of text.
validContexts.add(new NumberContext("Tel:", ".")); // Punctuation surrounds number.
@ -297,6 +321,184 @@ public class PhoneNumberMatcherTest extends TestCase {
findMatchesInContexts(validContexts, true, true);
}
public void testMatchesMultiplePhoneNumbersSeparatedByPhoneNumberPunctuation() throws Exception {
String text = "Call 650-253-4561 -- 455-234-3451";
String region = "US";
PhoneNumber number1 = new PhoneNumber();
number1.setCountryCode(phoneUtil.getCountryCodeForRegion(region));
number1.setNationalNumber(6502534561L);
PhoneNumberMatch match1 = new PhoneNumberMatch(5, "650-253-4561", number1);
PhoneNumber number2 = new PhoneNumber();
number2.setCountryCode(phoneUtil.getCountryCodeForRegion(region));
number2.setNationalNumber(4552343451L);
PhoneNumberMatch match2 = new PhoneNumberMatch(21, "455-234-3451", number2);
Iterator<PhoneNumberMatch> matches = phoneUtil.findNumbers(text, region).iterator();
assertEquals(match1, matches.next());
assertEquals(match2, matches.next());
}
public void testDoesNotMatchMultiplePhoneNumbersSeparatedWithNoWhiteSpace() throws Exception {
// No white-space found between numbers - neither is found.
String text = "Call 650-253-4561--455-234-3451";
String region = "US";
assertTrue(hasNoMatches(phoneUtil.findNumbers(text, region)));
}
/**
* Strings with number-like things that shouldn't be found under any level.
*/
private static final NumberTest[] IMPOSSIBLE_CASES = {
new NumberTest("12345", "US"),
new NumberTest("23456789", "US"),
new NumberTest("234567890112", "US"),
new NumberTest("650+253+1234", "US"),
new NumberTest("3/10/1984", "CA"),
new NumberTest("03/27/2011", "US"),
new NumberTest("31/8/2011", "US"),
new NumberTest("1/12/2011", "US"),
new NumberTest("10/12/82", "DE"),
};
/**
* Strings with number-like things that should only be found under "possible".
*/
private static final NumberTest[] POSSIBLE_ONLY_CASES = {
new NumberTest("abc8002345678", "US"),
// US numbers cannot start with 7 in the test metadata to be valid.
new NumberTest("7121115678", "US"),
// 'X' should not be found in numbers at leniencies stricter than POSSIBLE, unless it represents
// a carrier code or extension.
new NumberTest("1650 x 253 - 1234", "US"),
new NumberTest("650 x 253 - 1234", "US"),
new NumberTest("650x2531234", "US"),
};
/**
* Strings with number-like things that should only be found up to and including the "valid"
* leniency level.
*/
private static final NumberTest[] VALID_CASES = {
new NumberTest("65 02 53 00 00.", "US"),
new NumberTest("6502 538365", "US"),
new NumberTest("650//253-1234", "US"), // 2 slashes are illegal at higher levels
new NumberTest("650/253/1234", "US"),
new NumberTest("9002309. 158", "US"),
new NumberTest("21 7/8 - 14 12/34 - 5", "US"),
new NumberTest("12.1 - 23.71 - 23.45", "US"),
new NumberTest("1979-2011 100%", "US"),
new NumberTest("800 234 1 111x1111", "US"),
new NumberTest("+494949-4-94", "DE"), // National number in wrong format
};
/**
* Strings with number-like things that should only be found up to and including the
* "strict_grouping" leniency level.
*/
private static final NumberTest[] STRICT_GROUPING_CASES = {
new NumberTest("(415) 6667777", "US"),
new NumberTest("415-6667777", "US"),
// Should be found by strict grouping but not exact grouping, as the last two groups are
// formatted together as a block.
new NumberTest("800-2491234", "DE"),
};
/**
* Strings with number-like things that should found at all levels.
*/
private static final NumberTest[] EXACT_GROUPING_CASES = {
new NumberTest("\uFF14\uFF11\uFF15\uFF16\uFF16\uFF16\uFF17\uFF17\uFF17\uFF17", "US"),
new NumberTest("\uFF14\uFF11\uFF15-\uFF16\uFF16\uFF16-\uFF17\uFF17\uFF17\uFF17", "US"),
new NumberTest("4156667777", "US"),
new NumberTest("4156667777 x 123", "US"),
new NumberTest("415-666-7777", "US"),
new NumberTest("415/666-7777", "US"),
new NumberTest("415-666-7777 ext. 503", "US"),
new NumberTest("1 415 666 7777 x 123", "US"),
new NumberTest("+1 415-666-7777", "US"),
new NumberTest("+494949 49", "DE"),
new NumberTest("+49-49-34", "DE"),
new NumberTest("+49-4931-49", "DE"),
new NumberTest("04931-49", "DE"), // With National Prefix
new NumberTest("+49-494949", "DE"), // One group with country code
new NumberTest("+49-494949 ext. 49", "DE"),
new NumberTest("+49494949 ext. 49", "DE"),
new NumberTest("0494949", "DE"),
new NumberTest("0494949 ext. 49", "DE"),
};
public void testMatchesWithStrictGroupingLeniency() throws Exception {
int noMatchFoundCount = 0;
int wrongMatchFoundCount = 0;
List<NumberTest> testCases = new ArrayList<NumberTest>();
testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES));
testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES));
doTestNumberMatchesForLeniency(testCases, Leniency.STRICT_GROUPING);
}
public void testNonMatchesWithStrictGroupLeniency() throws Exception {
int matchFoundCount = 0;
List<NumberTest> testCases = new ArrayList<NumberTest>();
testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES));
testCases.addAll(Arrays.asList(VALID_CASES));
doTestNumberNonMatchesForLeniency(testCases, Leniency.STRICT_GROUPING);
}
public void testMatchesWithExactGroupingLeniency() throws Exception {
List<NumberTest> testCases = new ArrayList<NumberTest>();
testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES));
doTestNumberMatchesForLeniency(testCases, Leniency.EXACT_GROUPING);
}
public void testNonMatchesExactGroupLeniency() throws Exception {
List<NumberTest> testCases = new ArrayList<NumberTest>();
testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES));
testCases.addAll(Arrays.asList(VALID_CASES));
testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES));
doTestNumberNonMatchesForLeniency(testCases, Leniency.EXACT_GROUPING);
}
private void doTestNumberMatchesForLeniency(List<NumberTest> testCases,
PhoneNumberUtil.Leniency leniency) {
int noMatchFoundCount = 0;
int wrongMatchFoundCount = 0;
for (NumberTest test : testCases) {
Iterator<PhoneNumberMatch> iterator =
findNumbersForLeniency(test.rawString, test.region, leniency);
PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null;
if (match == null) {
noMatchFoundCount++;
System.err.println("No match found in " + test.toString() + " for leniency: " + leniency);
} else {
if (!test.rawString.equals(match.rawString())) {
wrongMatchFoundCount++;
System.err.println("Found wrong match in test " + test.toString() +
". Found " + match.rawString());
}
}
}
assertEquals(0, noMatchFoundCount);
assertEquals(0, wrongMatchFoundCount);
}
private void doTestNumberNonMatchesForLeniency(List<NumberTest> testCases,
PhoneNumberUtil.Leniency leniency) {
int matchFoundCount = 0;
for (NumberTest test : testCases) {
Iterator<PhoneNumberMatch> iterator =
findNumbersForLeniency(test.rawString, test.region, leniency);
PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null;
if (match != null) {
matchFoundCount++;
System.err.println("Match found in " + test.toString() + " for leniency: " + leniency);
}
}
assertEquals(0, matchFoundCount);
}
/**
* Helper method which tests the contexts provided and ensures that:
* -- if isValid is true, they all find a test number inserted in the middle when leniency of
@ -445,11 +647,10 @@ public class PhoneNumberMatcherTest extends TestCase {
numbers.append("My info: 415-666-7777 123 fake street");
}
// Only matches the first 5 despite there being 100 numbers due to max matches.
// There are two false positives per line as "123" is also tried.
// Only matches the first 10 despite there being 100 numbers due to max matches.
List<PhoneNumber> expected = new ArrayList<PhoneNumber>(100);
PhoneNumber number = phoneUtil.parse("+14156667777", null);
for (int i = 0; i < 5; i++) {
for (int i = 0; i < 10; i++) {
expected.add(number);
}
@ -572,7 +773,7 @@ public class PhoneNumberMatcherTest extends TestCase {
PhoneNumberMatch match = matches.next();
assertEquals(start - index, match.start());
assertEquals(end - index, match.end());
assertEquals(match.rawString(), sub.subSequence(match.start(), match.end()).toString());
assertEquals(sub.subSequence(match.start(), match.end()).toString(), match.rawString());
}
/**
@ -594,7 +795,7 @@ public class PhoneNumberMatcherTest extends TestCase {
* Tests valid numbers in contexts that should pass for {@link Leniency#POSSIBLE}.
*/
private void findPossibleInContext(String number, String defaultCountry) {
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(15);
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>();
contextPairs.add(new NumberContext("", "")); // no context
contextPairs.add(new NumberContext(" ", "\t")); // whitespace only
contextPairs.add(new NumberContext("Hello ", "")); // no context at end
@ -618,15 +819,9 @@ public class PhoneNumberMatcherTest extends TestCase {
// With dates, written in the American style.
contextPairs.add(new NumberContext(
"As I said on 03/10/2011, you may call me at ", ""));
contextPairs.add(new NumberContext(
"As I said on 03/27/2011, you may call me at ", ""));
contextPairs.add(new NumberContext(
"As I said on 31/8/2011, you may call me at ", ""));
contextPairs.add(new NumberContext(
"As I said on 1/12/2011, you may call me at ", ""));
contextPairs.add(new NumberContext(
"I was born on 10/12/82. Please call me at ", ""));
// With a postfix stripped off as it looks like the start of another number
// With trailing numbers after a comma. The 45 should not be considered an extension.
contextPairs.add(new NumberContext("", ", 45 days a year"));
// With a postfix stripped off as it looks like the start of another number.
contextPairs.add(new NumberContext("Call ", "/x12 more"));
doTestInContext(number, defaultCountry, contextPairs, Leniency.POSSIBLE);
@ -637,17 +832,18 @@ public class PhoneNumberMatcherTest extends TestCase {
* {@link Leniency#VALID}.
*/
private void findValidInContext(String number, String defaultCountry) {
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(5);
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>();
// With other small numbers.
contextPairs.add(new NumberContext("It's only 9.99! Call ", " to buy"));
// With a number Day.Month.Year date.
contextPairs.add(new NumberContext("Call me on 21.6.1984 at ", ""));
// With a number Month/Day date.
contextPairs.add(new NumberContext("Call me on 06/21 at ", ""));
// With a number Day.Month date
// With a number Day.Month date.
contextPairs.add(new NumberContext("Call me on 21.6. at ", ""));
// With a number Month/Day/Year date.
contextPairs.add(new NumberContext("Call me on 06/21/84 at ", ""));
doTestInContext(number, defaultCountry, contextPairs, Leniency.VALID);
}
@ -659,10 +855,10 @@ public class PhoneNumberMatcherTest extends TestCase {
int start = prefix.length();
int end = start + number.length();
Iterable<PhoneNumberMatch> iterable =
phoneUtil.findNumbers(text, defaultCountry, leniency, Long.MAX_VALUE);
Iterator<PhoneNumberMatch> iterator =
phoneUtil.findNumbers(text, defaultCountry, leniency, Long.MAX_VALUE).iterator();
PhoneNumberMatch match = iterable.iterator().hasNext() ? iterable.iterator().next() : null;
PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null;
assertNotNull("Did not find a number in '" + text + "'; expected '" + number + "'", match);
CharSequence extracted = text.subSequence(match.start(), match.end());
@ -691,9 +887,18 @@ public class PhoneNumberMatcherTest extends TestCase {
}
}
private Iterator<PhoneNumberMatch> findNumbersForLeniency(
String text, String defaultCountry, PhoneNumberUtil.Leniency leniency) {
return phoneUtil.findNumbers(text, defaultCountry, leniency, Long.MAX_VALUE).iterator();
}
/**
* Returns true if there were no matches found.
*/
private boolean hasNoMatches(Iterator<PhoneNumberMatch> iterator) {
return !iterator.hasNext();
}
private boolean hasNoMatches(Iterable<PhoneNumberMatch> iterable) {
return !iterable.iterator().hasNext();
}
@ -711,4 +916,22 @@ public class PhoneNumberMatcherTest extends TestCase {
this.trailingText = trailingText;
}
}
/**
* Small class that holds the number we want to test and the region for which it should be valid.
*/
private static class NumberTest {
final String rawString;
final String region;
NumberTest(String rawString, String regionCode) {
this.rawString = rawString;
this.region = regionCode;
}
@Override
public String toString() {
return rawString + " (" + region.toString() + ")";
}
}
}

+ 6
- 4
java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java View File

@ -147,7 +147,7 @@ public class PhoneNumberUtilTest extends TestCase {
assertEquals("(\\d{3})(\\d{3})(\\d{4})",
metadata.getNumberFormat(1).getPattern());
assertEquals("$1 $2 $3", metadata.getNumberFormat(1).getFormat());
assertEquals("[13-9]\\d{9}|2[0-35-9]\\d{8}",
assertEquals("[13-689]\\d{9}|2[0-35-9]\\d{8}",
metadata.getGeneralDesc().getNationalNumberPattern());
assertEquals("\\d{7}(?:\\d{3})?", metadata.getGeneralDesc().getPossibleNumberPattern());
assertTrue(metadata.getGeneralDesc().exactlySameAs(metadata.getFixedLine()));
@ -170,7 +170,7 @@ public class PhoneNumberUtilTest extends TestCase {
assertEquals("(\\d{3})(\\d{3,4})(\\d{4})",
metadata.getNumberFormat(5).getPattern());
assertEquals("$1 $2 $3", metadata.getNumberFormat(5).getFormat());
assertEquals("(?:[24-6]\\d{2}|3[03-9]\\d|[789](?:[1-9]\\d|0[2-9]))\\d{3,8}",
assertEquals("(?:[24-6]\\d{2}|3[03-9]\\d|[789](?:[1-9]\\d|0[2-9]))\\d{1,8}",
metadata.getFixedLine().getNationalNumberPattern());
assertEquals("\\d{2,14}", metadata.getFixedLine().getPossibleNumberPattern());
assertEquals("30123456", metadata.getFixedLine().getExampleNumber());
@ -1836,8 +1836,6 @@ public class PhoneNumberUtilTest extends TestCase {
// NSN matches.
assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH,
phoneUtil.isNumberMatch("+64 3 331-6005", "03 331 6005"));
assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH,
phoneUtil.isNumberMatch("3 331-6005", "03 331 6005"));
assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH,
phoneUtil.isNumberMatch(NZ_NUMBER, "03 331 6005"));
// Here the second number possibly starts with the country calling code for New Zealand,
@ -1872,6 +1870,10 @@ public class PhoneNumberUtilTest extends TestCase {
// Short NSN matches with the country not specified for either one or both numbers.
assertEquals(PhoneNumberUtil.MatchType.SHORT_NSN_MATCH,
phoneUtil.isNumberMatch("+64 3 331-6005", "331 6005"));
// We did not know that the "0" was a national prefix since neither number has a country code,
// so this is considered a SHORT_NSN_MATCH.
assertEquals(PhoneNumberUtil.MatchType.SHORT_NSN_MATCH,
phoneUtil.isNumberMatch("3 331-6005", "03 331 6005"));
assertEquals(PhoneNumberUtil.MatchType.SHORT_NSN_MATCH,
phoneUtil.isNumberMatch("3 331-6005", "331 6005"));
assertEquals(PhoneNumberUtil.MatchType.SHORT_NSN_MATCH,


BIN
java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE View File


BIN
java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_US View File


+ 8
- 9
java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java View File

@ -33,8 +33,8 @@ import java.util.TreeMap;
* @author Shaopeng Jia
*/
public class AreaCodeMapTest extends TestCase {
private final AreaCodeMap areaCodeMapForUS = new AreaCodeMap(1);
private final AreaCodeMap areaCodeMapForIT = new AreaCodeMap(39);
private final AreaCodeMap areaCodeMapForUS = new AreaCodeMap();
private final AreaCodeMap areaCodeMapForIT = new AreaCodeMap();
private PhoneNumber number = new PhoneNumber();
public AreaCodeMapTest() {
@ -83,13 +83,13 @@ public class AreaCodeMapTest extends TestCase {
public void testGetSmallerMapStorageChoosesDefaultImpl() {
AreaCodeMapStorageStrategy mapStorage =
new AreaCodeMap(1).getSmallerMapStorage(createDefaultStorageMapCandidate());
new AreaCodeMap().getSmallerMapStorage(createDefaultStorageMapCandidate());
assertFalse(mapStorage.isFlyweight());
}
public void testGetSmallerMapStorageChoosesFlyweightImpl() {
AreaCodeMapStorageStrategy mapStorage =
new AreaCodeMap(1).getSmallerMapStorage(createFlyweightStorageMapCandidate());
new AreaCodeMap().getSmallerMapStorage(createFlyweightStorageMapCandidate());
assertTrue(mapStorage.isFlyweight());
}
@ -158,21 +158,20 @@ public class AreaCodeMapTest extends TestCase {
* this stream. The resulting area code map is expected to be strictly equal to the provided one
* from which it was generated.
*/
private static AreaCodeMap createNewAreaCodeMap(AreaCodeMap areaCodeMap)
throws IOException {
private static AreaCodeMap createNewAreaCodeMap(AreaCodeMap areaCodeMap) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
areaCodeMap.writeExternal(objectOutputStream);
objectOutputStream.flush();
AreaCodeMap newAreaCodeMap = new AreaCodeMap(1);
AreaCodeMap newAreaCodeMap = new AreaCodeMap();
newAreaCodeMap.readExternal(
new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
return newAreaCodeMap;
}
public void testReadWriteExternalWithDefaultStrategy() throws IOException {
AreaCodeMap localAreaCodeMap = new AreaCodeMap(1);
AreaCodeMap localAreaCodeMap = new AreaCodeMap();
localAreaCodeMap.readAreaCodeMap(createDefaultStorageMapCandidate());
assertFalse(localAreaCodeMap.getAreaCodeMapStorage().isFlyweight());
@ -182,7 +181,7 @@ public class AreaCodeMapTest extends TestCase {
}
public void testReadWriteExternalWithFlyweightStrategy() throws IOException {
AreaCodeMap localAreaCodeMap = new AreaCodeMap(1);
AreaCodeMap localAreaCodeMap = new AreaCodeMap();
localAreaCodeMap.readAreaCodeMap(createFlyweightStorageMapCandidate());
assertTrue(localAreaCodeMap.getAreaCodeMapStorage().isFlyweight());


+ 80
- 0
java/test/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorageTest.java View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.i18n.phonenumbers.geocoding;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Unittests for FlyweightMapStorage.java
*
* @author Philippe Liard
*/
public class FlyweightMapStorageTest extends TestCase {
private final SortedMap<Integer, String> areaCodeMap = new TreeMap<Integer, String>();
public FlyweightMapStorageTest() {
areaCodeMap.put(331402, "Paris");
areaCodeMap.put(331434, "Paris");
areaCodeMap.put(334910, "Marseille");
areaCodeMap.put(334911, "Marseille");
}
public void testReadFromSortedMap() {
FlyweightMapStorage mapStorage = new FlyweightMapStorage();
mapStorage.readFromSortedMap(areaCodeMap);
assertEquals(331402, mapStorage.getPrefix(0));
assertEquals(331434, mapStorage.getPrefix(1));
assertEquals(334910, mapStorage.getPrefix(2));
assertEquals(334911, mapStorage.getPrefix(3));
String desc = mapStorage.getDescription(0);
assertEquals("Paris", desc);
assertTrue(desc == mapStorage.getDescription(1)); // Same identity.
desc = mapStorage.getDescription(2);
assertEquals("Marseille", desc);
assertTrue(desc == mapStorage.getDescription(3)); // Same identity.
}
public void testWriteAndReadExternal() throws IOException {
FlyweightMapStorage mapStorage = new FlyweightMapStorage();
mapStorage.readFromSortedMap(areaCodeMap);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
mapStorage.writeExternal(objectOutputStream);
objectOutputStream.flush();
FlyweightMapStorage newMapStorage = new FlyweightMapStorage();
ObjectInputStream objectInputStream =
new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
newMapStorage.readExternal(objectInputStream);
String expected = mapStorage.toString();
assertFalse(expected.length() == 0);
assertEquals(expected, newMapStorage.toString());
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save