Browse Source

JAVA: findNumbers functionality, metadata updates, bug-fixes

pull/567/head
Lara Scheidegger 15 years ago
committed by Mihaela Rosca
parent
commit
0c1ad33194
38 changed files with 2070 additions and 681 deletions
  1. +1
    -1
      java/build.xml
  2. +21
    -1
      java/release_notes.txt
  3. +7
    -3
      java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java
  4. +7
    -3
      java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java
  5. +25
    -7
      java/resources/com/google/i18n/phonenumbers/proto/phonenumber.proto
  6. +221
    -80
      java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml
  7. +10
    -2
      java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml
  8. +23
    -10
      java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
  9. +124
    -0
      java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java
  10. +339
    -0
      java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java
  11. +176
    -59
      java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
  12. +34
    -3
      java/src/com/google/i18n/phonenumbers/Phonenumber.java
  13. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF
  14. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO
  15. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR
  16. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL
  17. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO
  18. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR
  19. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK
  20. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FO
  21. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GE
  22. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KR
  23. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KW
  24. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LA
  25. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LU
  26. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MU
  27. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC
  28. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SH
  29. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR
  30. BIN
      java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE
  31. +38
    -4
      java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java
  32. +72
    -0
      java/test/com/google/i18n/phonenumbers/PhoneNumberMatchTest.java
  33. +540
    -0
      java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java
  34. +402
    -504
      java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
  35. +30
    -4
      java/test/com/google/i18n/phonenumbers/PhonenumberTest.java
  36. BIN
      java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_AR
  37. BIN
      java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE
  38. BIN
      java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_KR

+ 1
- 1
java/build.xml View File

@ -25,7 +25,7 @@
<target name="compile" description="Compile Java source.">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
<javac srcdir="${test.dir}" destdir="${classes.dir}" classpathref="classpath"/>
<javac srcdir="${test.dir}" destdir="${classes.dir}" classpathref="classpath" debug="on"/>
<javac srcdir="${resources.dir}" destdir="${classes.dir}" classpathref="classpath"/>
</target>


+ 21
- 1
java/release_notes.txt View File

@ -1,8 +1,28 @@
Mar 10th, 2011
* New functionality:
- New function to format a number with the preferred domestic carrier code used when parsing,
falling back to a default calling code otherwise (formatNationalNumberWithPreferredCarrierCode).
- We now store the preferred domestic carrier code used when the user calls parseAndKeepRawInput
- New functionality to extract phone-numbers from text (findNumbers). This is just the first
version - it does not extract ALPHA numbers such as 0800 CALL ME, or numbers where alternate
endings are specified (such as 03-331 6005/6006).
* Code changes:
- Tidying up the test file to use several pre-defined phone number constants
- Fixing several lint errors
- Added javadoc to formatNationalNumberWithCarrierCode
- Fixed bug where a null pointer exception was thrown when getAsYouTypeFormatter was called with an
invalid region code
- Improved AsYouTypeFormatter to deal with countries with variable-length patterns such as LU
* Metadata changes:
- Bug-fixes and updates for the following countries: BF, BO, BR, CL, CO, CR, DK, FO, GE, KR, KW,
LA, LU, MU, SC, SH, TR, VE
- New country: SH
Mar 10th, 2011
* Code changes:
- releasing the code to run the demo on localhost or appengine.
Mar 3rd, 2011
Mar 7th, 2011
* Metadata changes:
- Adding support for AC


+ 7
- 3
java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java View File

@ -123,9 +123,6 @@ public class BuildMetadataFromXml {
String preferredInternationalPrefix = element.getAttribute("preferredInternationalPrefix");
metadata.setPreferredInternationalPrefix(preferredInternationalPrefix);
}
String nationalPrefix = "";
String nationalPrefixFormattingRule = "";
String carrierCodeFormattingRule = "";
if (element.hasAttribute("nationalPrefixForParsing")) {
metadata.setNationalPrefixForParsing(
validateRE(element.getAttribute("nationalPrefixForParsing")));
@ -134,6 +131,8 @@ public class BuildMetadataFromXml {
validateRE(element.getAttribute("nationalPrefixTransformRule")));
}
}
String nationalPrefix = "";
String nationalPrefixFormattingRule = "";
if (element.hasAttribute("nationalPrefix")) {
nationalPrefix = element.getAttribute("nationalPrefix");
metadata.setNationalPrefix(nationalPrefix);
@ -144,6 +143,11 @@ public class BuildMetadataFromXml {
metadata.setNationalPrefixForParsing(nationalPrefix);
}
}
String carrierCodeFormattingRule = "";
if (element.hasAttribute("carrierCodeFormattingRule")) {
carrierCodeFormattingRule = validateRE(
getDomesticCarrierCodeFormattingRuleFromElement(element, nationalPrefix));
}
if (element.hasAttribute("preferredExtnPrefix")) {
metadata.setPreferredExtnPrefix(element.getAttribute("preferredExtnPrefix"));
}


+ 7
- 3
java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java View File

@ -35,6 +35,10 @@ import java.util.Map;
*/
public class BuildMetadataProtoFromXml {
private static final String PACKAGE_NAME = PhoneNumberUtil.class.getPackage().getName();
private static final String TEST_COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME =
"CountryCodeToRegionCodeMapForTesting";
private static final String COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME =
"CountryCodeToRegionCodeMap";
private static final String HELP_MESSAGE =
"Usage:\n" +
@ -54,7 +58,7 @@ public class BuildMetadataProtoFromXml {
" <outputDir>" + PhoneNumberUtil.META_DATA_FILE_PREFIX + "_*\n" +
"Mapping file will be stored in:\n" +
" <outputDir>/" + PACKAGE_NAME.replaceAll("\\.", "/") + "/" +
PhoneNumberUtil.COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME + ".java\n" +
COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME + ".java\n" +
"\n" +
"Example command line invocation:\n" +
"BuildMetadataProtoFromXml PhoneNumberMetadata.xml src false false\n";
@ -134,9 +138,9 @@ public class BuildMetadataProtoFromXml {
String outputDir, boolean forTesting) throws IOException {
String mappingClassName;
if (forTesting) {
mappingClassName = PhoneNumberUtilTest.TEST_COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME;
mappingClassName = TEST_COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME;
} else {
mappingClassName = PhoneNumberUtil.COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME;
mappingClassName = COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME;
}
String mappingFile =
outputDir + "/" + PACKAGE_NAME.replaceAll("\\.", "/") + "/" + mappingClassName + ".java";


+ 25
- 7
java/resources/com/google/i18n/phonenumbers/proto/phonenumber.proto View File

@ -24,6 +24,8 @@ option optimize_for = LITE_RUNTIME;
package i18n.phonenumbers;
message PhoneNumber {
// The country calling code for this number, as defined by the International Telecommunication Union
// (ITU). Fox example, this would be 1 for NANPA countries, and 33 for France.
required int32 country_code = 1;
// National (significant) Number is defined in International Telecommunication Union Recommendation
@ -41,16 +43,24 @@ message PhoneNumber {
// as there is no standard defined). However, only ASCII digits should be stored here.
optional string extension = 3;
// The leading zero in the national (significant) number of an Italian phone number has a special
// meaning. Unlike the rest of the world, it indicates the number is a fixed-line number. There have
// been plans to migrate fixed-line numbers to start with the digit two since December 2000, but it
// has not happened yet. See http://en.wikipedia.org/wiki/%2B39 for more details.
// In some countries, the national (significant) number starts with a "0" without this being a
// national prefix or trunk code of some kind. For example, the leading zero in the national
// (significant) number of an Italian phone number indicates the number is a fixed-line number.
// There have been plans to migrate fixed-line numbers to start with the digit two since December
// 2000, but it has not happened yet. See http://en.wikipedia.org/wiki/%2B39 for more details.
//
// This field can be safely ignored (no need to set it) if you are not dealing with Italian
// phone numbers. For an Italian phone number, if its national (significant) number starts
// with the digit zero, set this flag to true.
// This field can be safely ignored (there is no need to set it) for most countries. Some limited
// amount of countries behave like Italy - for these cases, if the leading zero of a number would be
// retained even when dialling internationally, set this flag to true.
//
// Clients who use the parsing functionality of the i18n phone number libraries
// will have this field set if necessary automatically.
optional bool italian_leading_zero = 4;
// The next few fields are non-essential fields for a phone number. They retain extra information
// about the form the phone number was in when it was provided to us to parse. They can be safely
// ignored by most clients.
// This field is used to store the raw input string containing phone numbers before it was
// canonicalized by the library. For example, it could be used to store alphanumerical numbers
// such as "1-800-GOOG-411".
@ -80,6 +90,14 @@ message PhoneNumber {
// The source from which the country_code is derived.
optional CountryCodeSource country_code_source = 6;
// The carrier selection code that is preferred when calling this phone number domestically. This
// also includes codes that need to be dialed in some countries when calling from landlines to
// mobiles or vice versa. For example, in Columbia, a "3" needs to be dialed before the phone number
// itself when calling from a mobile phone to a domestic landline phone and vice versa.
//
// Note this is the "preferred" code, which means other codes may work as well.
optional string preferred_domestic_carrier_code = 7;
}
// Examples


+ 221
- 80
java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml View File

@ -1842,9 +1842,10 @@
7(?:
[024-6]\d|
1[0-489]|
3[01]|
3[0124]|
7[01]|
8[013-9]|
9[012]
9[0-4]
)\d{5}
</nationalNumberPattern>
<exampleNumber>70123456</exampleNumber>
@ -2265,7 +2266,8 @@
<territory id="BO" countryCode="591"
internationalPrefix="00(1\d)?"
nationalPrefix="0"
nationalPrefixForParsing="0(1\d)?">
nationalPrefixForParsing="0(1\d)?"
carrierCodeFormattingRule="$NP$CC $FG">
<availableFormats>
<numberFormat pattern="([234])(\d{7})">
<leadingDigits>[234]</leadingDigits>
@ -2316,8 +2318,8 @@
<territory id="BR" countryCode="55"
internationalPrefix="00(?:1[45]|2[135]|[34]1|43)"
nationalPrefix="0"
nationalPrefixForParsing="0(?:(?:1[245]|2[135]|[34]1)(\d{10}))?"
nationalPrefixTransformRule="$1">
nationalPrefixForParsing="0(?:(1[245]|2[135]|[34]1)(\d{10}))?"
nationalPrefixTransformRule="$2">
<!--The national prefix for parsing here also contains a capturing group for the main number,
since the carrier codes here may also be area codes, so we want to check the length of
the number after capturing. We also need a nationalTransformRule to repopulate with the
@ -2325,7 +2327,7 @@
<availableFormats>
<numberFormat nationalPrefixFormattingRule="($FG)"
pattern="(\d{2})(\d{4})(\d{4})"
carrierCodeFormattingRule="$NP $CC $FG">
carrierCodeFormattingRule="$NP $CC ($FG)">
<leadingDigits>[1-9][1-9]</leadingDigits>
<format>$1 $2-$3</format>
</numberFormat>
@ -3100,18 +3102,21 @@
<territory id="CL" countryCode="56"
internationalPrefix="(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0"
nationalPrefix="0"
nationalPrefixForParsing="0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018])"
nationalPrefixForParsing="0|(1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))"
nationalPrefixFormattingRule="$NP$FG">
<!-- When dialling mobile numbers from landlines, or vice versa, you need
a prefix of 0, which we strip here. National destinations may be dialled
with a carrier if they are not local so we strip these carrier codes as
well. -->
<!-- When dialling mobile numbers from landlines, or vice versa, you need a prefix of 0, which
we strip here. National destinations may be dialled with a carrier if they are not local so
we extract these carrier codes as well. -->
<availableFormats>
<numberFormat pattern="(2)(\d{3})(\d{4})">
<numberFormat pattern="(2)(\d{3})(\d{4})"
nationalPrefixFormattingRule="($FG)"
carrierCodeFormattingRule="$CC ($FG)">
<leadingDigits>2</leadingDigits>
<format>$1 $2 $3</format>
</numberFormat>
<numberFormat pattern="(\d{2})(\d{2,3})(\d{4})">
<numberFormat pattern="(\d{2})(\d{2,3})(\d{4})"
nationalPrefixFormattingRule="($FG)"
carrierCodeFormattingRule="$CC ($FG)">
<leadingDigits>
[357]|
4[1-35]|
@ -3668,9 +3673,10 @@
<!-- http://www.itu.int/oth/T020200002C/en
http://en.wikipedia.org/wiki/Telephone_numbers_in_Colombia -->
<territory id="CO" countryCode="57" internationalPrefix="00[579]|#555|#999"
nationalPrefix="0" nationalPrefixForParsing="0(?:[3579]|4(?:44|56))">
nationalPrefix="0" nationalPrefixForParsing="0([3579]|4(?:44|56))">
<availableFormats>
<numberFormat pattern="(\d)(\d{7})">
<numberFormat pattern="(\d)(\d{7})" carrierCodeFormattingRule="$NP$CC $FG"
nationalPrefixFormattingRule="($FG)">
<leadingDigits>
1(?:
8[2-9]|
@ -3690,7 +3696,7 @@
)|
[24-8]
</leadingDigits>
<format>($1) $2</format>
<format>$1 $2</format>
</numberFormat>
<intlNumberFormat pattern="(\d)(\d{7})">
<leadingDigits>
@ -3714,7 +3720,7 @@
</leadingDigits>
<format>$1 $2</format>
</intlNumberFormat>
<numberFormat pattern="(\d{3})(\d{7})">
<numberFormat pattern="(\d{3})(\d{7})" carrierCodeFormattingRule="$NP$CC $FG">
<leadingDigits>3</leadingDigits>
<format>$1 $2</format>
</numberFormat>
@ -3803,11 +3809,12 @@
<!-- Costa Rica -->
<!-- http://www.itu.int/oth/T0202000030/en -->
<territory id="CR" countryCode="506" internationalPrefix="00">
<territory id="CR" countryCode="506" internationalPrefix="00"
nationalPrefixForParsing="(1900)" carrierCodeFormattingRule="$CC $FG">
<availableFormats>
<numberFormat pattern="(\d{4})(\d{4})">
<leadingDigits>
2|
[24]|
8[389]
</leadingDigits>
<format>$1 $2</format>
@ -3818,7 +3825,7 @@
</numberFormat>
</availableFormats>
<generalDesc>
<nationalNumberPattern>[289]\d{7,9}</nationalNumberPattern>
<nationalNumberPattern>[2489]\d{7,9}</nationalNumberPattern>
<possibleNumberPattern>\d{8,10}</possibleNumberPattern>
</generalDesc>
<fixedLine>
@ -3832,16 +3839,40 @@
<exampleNumber>83123456</exampleNumber>
</mobile>
<tollFree>
<nationalNumberPattern>800\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>8001234567</exampleNumber>
<nationalNumberPattern>800\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>8001234567</exampleNumber>
</tollFree>
<premiumRate>
<!-- Includes "mass calls" numbers with prefix 905. -->
<nationalNumberPattern>90[059]\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>9001234567</exampleNumber>
<!-- Includes "mass calls" numbers with prefix 905. -->
<nationalNumberPattern>90[059]\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>9001234567</exampleNumber>
</premiumRate>
<voip>
<nationalNumberPattern>4000\d{4}</nationalNumberPattern>
<possibleNumberPattern>\d{8}</possibleNumberPattern>
<exampleNumber>40001234</exampleNumber>
</voip>
<shortCode>
<!-- This pattern excludes 4-digit SMS content numbers for now. -->
<nationalNumberPattern>
1(?:
02[2-469]|
1(?:
1[0235-9]|
2|
37|
46|
75|
8[79]|
9[0-379]
)|
212)
</nationalNumberPattern>
<possibleNumberPattern>\d{3,4}</possibleNumberPattern>
<exampleNumber>1022</exampleNumber>
</shortCode>
</territory>
<!-- Cuba -->
@ -4348,7 +4379,8 @@
4[0-2]|
5[0-3]|
6[01]|
72|
7[12]|
81|
99
)\d{6}
</nationalNumberPattern>
@ -5207,7 +5239,12 @@
<!-- Faroe Islands -->
<!-- http://www.itu.int/oth/T0202000047/en -->
<territory id="FO" countryCode="298" internationalPrefix="00"
nationalPrefixForParsing="10(?:01|[12]0|88)">
nationalPrefixForParsing="(10(?:01|[12]0|88))"
carrierCodeFormattingRule="$CC $FG">
<!-- All numbers are formatted together, as a block. -->
<numberFormat pattern="(\d{6})">
<format>$1</format>
</numberFormat>
<generalDesc>
<nationalNumberPattern>[2-9]\d{5}</nationalNumberPattern>
<possibleNumberPattern>\d{6}</possibleNumberPattern>
@ -6113,25 +6150,22 @@
<!-- Format isn't very strictly defined - the yellow pages omits area code and does 2 2 2,
the chairman on the communications commission listed his as 2 2 4 (Tblisi area
code). -->
<numberFormat pattern="(32)(\d{2})(\d{2})(\d{2})">
<numberFormat pattern="(32)(\d{2})(\d{2})(\d{2})"
nationalPrefixFormattingRule="$NP ($FG)">
<leadingDigits>32</leadingDigits>
<format>$1 $2 $3 $4</format>
</numberFormat>
<numberFormat pattern="(\d{3})(\d{5})">
<numberFormat pattern="(\d{3})(\d)(\d{2})(\d{2})"
nationalPrefixFormattingRule="$NP ($FG)">
<leadingDigits>
2|
3[13-79]|
446
[24]|
3[13-79]
</leadingDigits>
<format>$1 $2</format>
</numberFormat>
<numberFormat pattern="(\d{4})(\d{3,4})">
<leadingDigits>44[2-5]</leadingDigits>
<format>$1 $2</format>
<format>$1 $2 $3 $4</format>
</numberFormat>
<numberFormat pattern="(\d{2})(\d{3})(\d{3})">
<numberFormat pattern="(\d{2})(\d{2})(\d{2})(\d{2})">
<leadingDigits>[5679]</leadingDigits>
<format>$1 $2 $3</format>
<format>$1 $2 $3 $4</format>
</numberFormat>
<numberFormat pattern="(800)(\d{2})(\d{2})(\d{2})">
<leadingDigits>8</leadingDigits>
@ -6140,33 +6174,46 @@
</availableFormats>
<generalDesc>
<nationalNumberPattern>
[1-3579]\d{7}|
[1-579]\d{7}|
8\d{8}
</nationalNumberPattern>
<possibleNumberPattern>\d{3,9}</possibleNumberPattern>
<possibleNumberPattern>\d{5,9}</possibleNumberPattern>
</generalDesc>
<fixedLine>
<!-- Added the 253 and 250 prefixes as there are lots of numbers on the internet with that
prefix -->
<!-- Added the 250, 253 and 255 prefixes as there are lots of numbers on the internet with
that prefix. We also list the old prefixes first, and then the new prefixes, to make it
easier to delete the old prefixes at the end of May 2011. -->
<nationalNumberPattern>
(?:
122|
2(?:
22|
36|
5[03]
5[035]
)|
3(?:
1[0-35-8]|
[24-6]\d|
3[1-35679]|
4\d|
7[0-39]|
9[1-35-7]
)|
44[2-6]
3(?:
[256]\d|
4[124-9]|
7[0-4]
)|
4(?:
1\d|
2[2-7]|
3[1-79]|
4[2-8]|
7[239]|
9[1-7]
)
)\d{5}
</nationalNumberPattern>
<possibleNumberPattern>\d{3,8}</possibleNumberPattern>
<possibleNumberPattern>\d{5,8}</possibleNumberPattern>
<exampleNumber>32123456</exampleNumber>
</fixedLine>
<mobile>
@ -10264,8 +10311,8 @@
nationalPrefix
1[4-6]XX-YYYY - Country-wide common number services, display as it is without hyphens -->
<territory id="KR" countryCode="82" internationalPrefix="00(?:[124-68]|[37]\d{2})"
nationalPrefix="0" nationalPrefixForParsing="0(?:8[1-46-8]|85\d{2})?"
nationalPrefixFormattingRule="$NP$FG">
nationalPrefix="0" nationalPrefixForParsing="0(8[1-46-8]|85\d{2})?"
nationalPrefixFormattingRule="$NP$FG" carrierCodeFormattingRule="$NP$CC-$FG">
<availableFormats>
<numberFormat pattern="(\d{2})(\d{4})(\d{4})">
<leadingDigits>
@ -10540,7 +10587,7 @@
5\d
)|
6(?:
0[03679]|
0[034679]|
5[015-9]|
6\d|
7[067]
@ -10811,12 +10858,8 @@
<territory id="LA" countryCode="856" internationalPrefix="00"
nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG">
<availableFormats>
<numberFormat pattern="(20)([23])(\d{3})(\d{3})">
<leadingDigits>20[23]</leadingDigits>
<format>$1 $2 $3 $4</format>
</numberFormat>
<numberFormat pattern="(20)([579]\d)(\d{3})(\d{3})">
<leadingDigits>20[579]</leadingDigits>
<numberFormat pattern="(20)(\d{2})(\d{3})(\d{3})">
<leadingDigits>20</leadingDigits>
<format>$1 $2 $3 $4</format>
</numberFormat>
<numberFormat pattern="([2-57]\d)(\d{3})(\d{3})">
@ -10844,14 +10887,14 @@
<mobile>
<nationalNumberPattern>
20(?:
[23]|
2[23]|
5[4-6]|
77|
9[89]
)\d{6}
</nationalNumberPattern>
<possibleNumberPattern>\d{9,10}</possibleNumberPattern>
<exampleNumber>202345678</exampleNumber>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>2023123456</exampleNumber>
</mobile>
<!-- No information on other types of phone numbers for Lao P.D.R. has been found. -->
</territory>
@ -11316,7 +11359,8 @@
<!-- http://www.ilr.public.lu/communications_electroniques/numerotation/index.html
-->
<territory id="LU" countryCode="352" internationalPrefix="00"
nationalPrefixForParsing="15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\d">
nationalPrefixForParsing="(15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\d)"
carrierCodeFormattingRule="$CC $FG">
<availableFormats>
<!-- Patterns overlap because of variable number length. -->
<numberFormat pattern="(\d{2})(\d{3})">
@ -12517,7 +12561,8 @@
<!-- Mauritius -->
<!-- http://www.itu.int/oth/T0202000088/en - covers mobile only -->
<!-- http://www.icta.mu/telecommunications/numbering.htm -->
<territory id="MU" countryCode="230" internationalPrefix="020">
<territory id="MU" countryCode="230" internationalPrefix="0(?:[2-7]0|33)"
preferredInternationalPrefix="020">
<!-- There is a proposal to change this to 8 digits - this is supposed to happen August 2010,
and 7 digit numbers will be phased out by 1 November 2010. Update Aug 9th: Changeover
postponed to indeterminate later date. -->
@ -12718,8 +12763,8 @@
nationalPrefixFormattingRule="$NP $FG">
<!-- When a number starts with 01 or 02, we remove the prefixes; when a number starts with 045
or 046 followed by 10 digits, we replace the prefixes with 1. This way all the mobile
numbers, regardless written in international format (leading 1) or national format
(leading 045/046), will be parsed into the same form. -->
numbers, regardless of whether they are written in international format (leading 1) or
national format (leading 045/046), will be parsed into the same form. -->
<availableFormats>
<numberFormat pattern="([358]\d)(\d{4})(\d{4})">
<leadingDigits>
@ -15184,13 +15229,13 @@
<exampleNumber>800000</exampleNumber>
</tollFree>
<voip>
<!-- The first digit is optional until the end of Feb 2011, when it will be compulsory. -->
<nationalNumberPattern>
(?:
4?4[1-3]|
6?47
44[1-3]|
647
)\d{4}
</nationalNumberPattern>
<possibleNumberPattern>\d{7}</possibleNumberPattern>
<exampleNumber>4410123</exampleNumber>
</voip>
</territory>
@ -15586,8 +15631,41 @@
</shortCode>
</territory>
<!-- Saint Helena -->
<!-- Saint Helena and Tristan da Cunha -->
<!-- http://www.itu.int/oth/T02020000AF/en -->
<territory id="SH" countryCode="290" internationalPrefix="00">
<!-- Numbers are formatted as a block. -->
<generalDesc>
<nationalNumberPattern>[2-9]\d{3}</nationalNumberPattern>
<possibleNumberPattern>\d{4}</possibleNumberPattern>
</generalDesc>
<fixedLine>
<nationalNumberPattern>
(?:
[2-468]\d|
7[01]
)\d{2}
</nationalNumberPattern>
<!-- Using St Helena Tourism as the example number. -->
<exampleNumber>2158</exampleNumber>
</fixedLine>
<mobile>
<nationalNumberPattern>NA</nationalNumberPattern>
<possibleNumberPattern>NA</possibleNumberPattern>
</mobile>
<premiumRate>
<nationalNumberPattern>
(?:
[59]\d|
7[2-9]
)\d{2}
</nationalNumberPattern>
<exampleNumber>5012</exampleNumber>
</premiumRate>
<shortCode>
<nationalNumberPattern>1\d{2,3}</nationalNumberPattern>
<possibleNumberPattern>\d{3,4}</possibleNumberPattern>
</shortCode>
</territory>
<!-- Slovenia -->
@ -16565,36 +16643,98 @@
<!-- Turkey -->
<!-- http://en.wikipedia.org/wiki/%2B90 -->
<!-- http://www.itu.int/oth/T02020000D6/en -->
<territory id="TR" countryCode="90" internationalPrefix="00"
nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG">
<territory id="TR" countryCode="90" internationalPrefix="00" nationalPrefix="0">
<availableFormats>
<numberFormat pattern="([2-589]\d{2})(\d{3})(\d{4})">
<numberFormat nationalPrefixFormattingRule="($NP$FG)" pattern="(\d{3})(\d{3})(\d{4})">
<leadingDigits>
[23]|
4(?:
[0-35-9]|
4[0-35-9]
)
</leadingDigits>
<format>$1 $2 $3</format>
</numberFormat>
<numberFormat nationalPrefixFormattingRule="$NP$FG" pattern="(\d{3})(\d{3})(\d{4})">
<leadingDigits>[589]</leadingDigits>
<format>$1 $2 $3</format>
</numberFormat>
<numberFormat pattern="(444)(\d{1})(\d{3})">
<leadingDigits>444</leadingDigits>
<format>$1 $2 $3</format>
</numberFormat>
</availableFormats>
<generalDesc>
<nationalNumberPattern>[2-589]\d{9}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<nationalNumberPattern>
[2-589]\d{9}|
444\d{4}
</nationalNumberPattern>
<possibleNumberPattern>\d{7,10}</possibleNumberPattern>
</generalDesc>
<fixedLine>
<!-- Includes numbers starting with 392 for Northern Cyprus. -->
<nationalNumberPattern>
[2-4]\d{9}|
850\d{7}
(?:
2(?:
[13][26]|
[28][2468]|
[45][268]|
[67][246]
)|
3(?:
[13][28]|
[24-6][2468]|
[78][02468]|
92
)|
4(?:
[16][246]|
[23578][2468]|
4[26]
)
)\d{7}
</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>2123456789</exampleNumber>
</fixedLine>
<mobile>
<nationalNumberPattern>5\d{9}</nationalNumberPattern>
<exampleNumber>5123456789</exampleNumber>
<nationalNumberPattern>
5(?:
0[1-35-7]|
22|
3\d|
4[1-79]|
5[1-5]|
9[246]
)\d{7}
</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>5012345678</exampleNumber>
</mobile>
<paging>
<nationalNumberPattern>512\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>5123456789</exampleNumber>
</paging>
<tollFree>
<nationalNumberPattern>800\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>8001234567</exampleNumber>
</tollFree>
<premiumRate>
<nationalNumberPattern>900\d{7}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>9001234567</exampleNumber>
</premiumRate>
<uan>
<!-- http://www.turktelekom.com.tr/tt/portal/News/Archive/7-digit-special-service-number-starting-with-444 -->
<nationalNumberPattern>
444\d{4}|
850\d{7}
</nationalNumberPattern>
<possibleNumberPattern>\d{7,10}</possibleNumberPattern>
<exampleNumber>4441444</exampleNumber>
</uan>
</territory>
<!-- Trinidad and Tobago -->
@ -17458,8 +17598,9 @@
<!-- http://en.wikipedia.org/wiki/+58 -->
<!-- 1XX specifies a particular carrier to route a call to. -->
<territory id="VE" countryCode="58" internationalPrefix="00"
nationalPrefix="0" nationalPrefixForParsing="1\d{2}|0"
nationalPrefixFormattingRule="$NP$FG">
nationalPrefix="0" nationalPrefixForParsing="(1\d{2})|0"
nationalPrefixFormattingRule="$NP$FG"
carrierCodeFormattingRule="$CC $FG">
<availableFormats>
<numberFormat pattern="(\d{3})(\d{7})">
<format>$1-$2</format>


+ 10
- 2
java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml View File

@ -72,7 +72,7 @@
<format>$1 15 $2-$3</format>
</numberFormat>
<numberFormat pattern="9(\d{4})(\d{2})(\d{4})"
carrierCodeFormattingRule="$FG $CC">
carrierCodeFormattingRule="$NP$FG $CC">
<leadingDigits>9(?:1[02-9]|[23])</leadingDigits>
<format>$1 $2-$3</format>
</numberFormat>
@ -196,6 +196,14 @@
<leadingDigits>[34]0|[68]9</leadingDigits>
<format>$1 $2</format>
</numberFormat>
<!-- Extra fictional pattern for shorter numbers with the same prefixes as the following
pattern, to illustrate the problem the AYTF has with real patterns that share this
property. -->
<numberFormat pattern="([4-9]\d)(\d{2})">
<leadingDigits>[4-9]</leadingDigits>
<leadingDigits>[4-6]|[7-9](?:\d[1-9]|[1-9]\d)</leadingDigits>
<format>$1 $2</format>
</numberFormat>
<numberFormat pattern="([4-9]\d{3})(\d{2,7})">
<leadingDigits>[4-9]</leadingDigits>
<leadingDigits>[4-6]|[7-9](?:\d[1-9]|[1-9]\d)</leadingDigits>
@ -372,7 +380,7 @@
nationalPrefix
1[4-6]XX-YYYY - Country-wide common number services, display as it is without hyphens -->
<territory id="KR" countryCode="82" internationalPrefix="00(?:[124-68]|[37]\d{2})"
nationalPrefix="0" nationalPrefixForParsing="0(?:8[1-46-8]|85\d{2})?"
nationalPrefix="0" nationalPrefixForParsing="0(8[1-46-8]|85\d{2})?"
nationalPrefixFormattingRule="$NP$FG">
<availableFormats>
<numberFormat pattern="(\d{2})(\d{4})(\d{4})">


+ 23
- 10
java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java View File

@ -55,7 +55,7 @@ public class AsYouTypeFormatter {
// A pattern that is used to match character classes in regular expressions. An example of a
// character class is [1-4].
private final Pattern CHARACTER_CLASS_PATTERN = Pattern.compile("\\[([^\\[\\]])*\\]");
private static final Pattern CHARACTER_CLASS_PATTERN = Pattern.compile("\\[([^\\[\\]])*\\]");
// Any digit in a regular expression that actually denotes a digit. For example, in the regular
// expression 80[0-2]\d{6,10}, the first 2 digits (8 and 0) are standalone digits, but the rest
// are not.
@ -89,8 +89,8 @@ public class AsYouTypeFormatter {
private RegexCache regexCache = new RegexCache(64);
/**
* Constructs a light-weight formatter which does no formatting, but outputs exactly what is
* fed into the inputDigit method.
* Constructs an as-you-type formatter. Should be obtained from {@link
* PhoneNumberUtil#getAsYouTypeFormatter}.
*
* @param regionCode the country/region where the phone number is being entered
*/
@ -102,6 +102,11 @@ public class AsYouTypeFormatter {
private void initializeCountrySpecificInfo(String regionCode) {
currentMetaData = phoneUtil.getMetadataForRegion(regionCode);
if (currentMetaData == null) {
// Set to a default instance of the metadata. This allows us to function with an incorrect
// region code, even if formatting only works for numbers specified with "+".
currentMetaData = new PhoneMetadata().setInternationalPrefix("NA");
}
nationalPrefixForParsing =
regexCache.getPatternForRegex(currentMetaData.getNationalPrefixForParsing());
internationalPrefix =
@ -170,8 +175,12 @@ public class AsYouTypeFormatter {
// Replace any standalone digit (not the one in d{}) with \d
numberPattern = STANDALONE_DIGIT_PATTERN.matcher(numberPattern).replaceAll("\\\\d");
formattingTemplate.setLength(0);
formattingTemplate.append(getFormattingTemplate(numberPattern, numberFormat));
return true;
String tempTemplate = getFormattingTemplate(numberPattern, numberFormat);
if (tempTemplate.length() > nationalNumber.length()) {
formattingTemplate.append(tempTemplate);
return true;
}
return false;
}
// Gets a formatting template which could be used to efficiently format a partial number where
@ -236,7 +245,7 @@ public class AsYouTypeFormatter {
return currentOutput;
}
@SuppressWarnings(value = "fallthrough")
@SuppressWarnings("fallthrough")
private String inputDigitWithOptionToRememberPosition(char nextChar, boolean rememberPosition) {
accruedInput.append(nextChar);
if (rememberPosition) {
@ -323,8 +332,7 @@ public class AsYouTypeFormatter {
if (!ableToFormat) {
return originalPosition;
}
int accruedInputIndex = 0;
int currentOutputIndex = 0;
int accruedInputIndex = 0, currentOutputIndex = 0;
int currentOutputLength = currentOutput.length();
while (accruedInputIndex < positionToRemember && currentOutputIndex < currentOutputLength) {
if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
@ -466,8 +474,13 @@ public class AsYouTypeFormatter {
formattingTemplate.replace(0, tempTemplate.length(), tempTemplate);
lastMatchPosition = digitMatcher.start();
return formattingTemplate.substring(0, lastMatchPosition + 1);
} else { // More digits are entered than we could handle.
ableToFormat = false;
} else {
if (possibleFormats.size() == 1) {
// More digits are entered than we could handle, and there are no other valid patterns to
// try.
ableToFormat = false;
} // else, we just reset the formatting pattern.
currentFormattingPattern = "";
return accruedInput.toString();
}
}


+ 124
- 0
java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java View File

@ -0,0 +1,124 @@
/*
* 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;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.util.Arrays;
/**
* The immutable match of a phone number within a piece of text. Matches may be found using
* {@link PhoneNumberUtil#findNumbers}.
*
* <p>A match consists of the {@linkplain #number() phone number} as well as the
* {@linkplain #start() start} and {@linkplain #end() end} offsets of the corresponding subsequence
* of the searched text. Use {@link #rawString()} to obtain a copy of the matched subsequence.
*
* <p>The following annotated example clarifies the relationship between the searched text, the
* match offsets, and the parsed number:
* <pre>
* CharSequence text = "Call me at +1 425 882-8080 for details.";
* RegionCode country = RegionCode.US;
* PhoneNumberUtil util = PhoneNumberUtil.getInstance();
*
* // Find the first phone number match:
* PhoneNumberMatch m = util.findNumbers(text, country).iterator().next();
*
* // rawString() contains the phone number as it appears in the text.
* "+1 425 882-8080".equals(m.rawString());
*
* // start() and end() define the range of the matched subsequence.
* CharSequence subsequence = text.subSequence(m.start(), m.end());
* "+1 425 882-8080".contentEquals(subsequence);
*
* // number() returns the the same result as PhoneNumberUtil.{@link PhoneNumberUtil#parse parse()}
* // invoked on rawString().
* util.parse(m.rawString(), country).equals(m.number());
* </pre>
*
* @author Tom Hofmann
*/
public final class PhoneNumberMatch {
/** The start index into the text. */
private final int start;
/** The raw substring matched. */
private final String match;
/** The matched phone number. */
private final PhoneNumber number;
/**
* Creates a new match.
*
* @param start the start index into the target text
* @param match the matched substring of the target text
* @param number the matched phone number
*/
PhoneNumberMatch(int start, String match, PhoneNumber number) {
if (start < 0) {
throw new IllegalArgumentException("Start index must be >= 0.");
}
if (match == null || number == null) {
throw new NullPointerException();
}
this.start = start;
this.match = match;
this.number = number;
}
/** Returns the phone number matched by the receiver. */
public PhoneNumber number() {
return number;
}
/** Returns the start index of the matched phone number within the searched text. */
public int start() {
return start;
}
/** Returns the exclusive end index of the matched phone number within the searched text. */
public int end() {
return start + match.length();
}
/** Returns the raw string matched as a phone number in the searched text. */
public String rawString() {
return match;
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[]{start, match, number});
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PhoneNumberMatch)) {
return false;
}
PhoneNumberMatch other = (PhoneNumberMatch) obj;
return match.equals(other.match) && (start == other.start) && number.equals(other.number);
}
@Override
public String toString() {
return "PhoneNumberMatch [" + start() + "," + end() + ") " + match;
}
}

+ 339
- 0
java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java View File

@ -0,0 +1,339 @@
/*
* 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;
import com.google.i18n.phonenumbers.PhoneNumberUtil.Leniency;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A stateful class that finds and extracts telephone numbers from {@linkplain CharSequence text}.
* Instances can be created using the {@linkplain PhoneNumberUtil#findNumbers factory methods} in
* {@link PhoneNumberUtil}.
*
* <p>Vanity numbers (phone numbers using alphabetic digits such as <tt>1-800-SIX-FLAGS</tt> are
* not found.
*
* <p>This class is not thread-safe.
*
* @author Tom Hofmann
*/
final class PhoneNumberMatcher implements Iterator<PhoneNumberMatch> {
/**
* The phone number pattern used by {@link #find}, similar to
* {@code PhoneNumberUtil.VALID_PHONE_NUMBER}, but with the following differences:
* <ul>
* <li>All captures are limited in order to place an upper bound to the text matched by the
* pattern.
* <ul>
* <li>Leading punctuation / plus signs are limited.
* <li>Consecutive occurrences of punctuation are limited.
* <li>Number of digits is limited.
* </ul>
* <li>No whitespace is allowed at the start or end.
* <li>No alpha digits (vanity numbers such as 1-800-SIX-FLAGS) are currently supported.
* </ul>
*/
private static final Pattern PATTERN;
/**
* A phone number pattern that does not allow whitespace as punctuation. This pattern is only used
* in a second attempt to find a phone number occurring in the context of other numbers, such as
* when the preceding or following token is a zip code.
*/
private static final Pattern INNER;
/**
* Matches strings that look like publication pages. Example:
* <pre>Computing Complete Answers to Queries in the Presence of Limited Access Patterns.
* Chen Li. VLDB J. 12(3): 211-227 (2003).</pre>
*
* The string "211-227 (2003)" is not a telephone number.
*/
private static final Pattern PUB_PAGES = Pattern.compile("\\d{1,5}-+\\d{1,5}\\s{0,4}\\(\\d{1,4}");
static {
/* Builds the PATTERN and INNER regular expression patterns. The building blocks below
* exist to make the patterns more easily understood. */
/* Limit on the number of leading (plus) characters. */
String leadLimit = limit(0, 2);
/* Limit on the number of consecutive punctuation characters. */
String punctuationLimit = limit(0, 4);
/* The maximum number of digits allowed in a digit-separated block. As we allow all digits in a
* single block, set high enough to accommodate the entire national number and the international
* country code. */
int digitBlockLimit =
PhoneNumberUtil.MAX_LENGTH_FOR_NSN + PhoneNumberUtil.MAX_LENGTH_COUNTRY_CODE;
/* Limit on the number of blocks separated by punctuation. Use digitBlockLimit since in some
* formats use spaces to separate each digit. */
String blockLimit = limit(0, digitBlockLimit);
/* Same as {@link PhoneNumberUtil#VALID_PUNCTUATION} but without space characters. */
String nonSpacePunctuationChars = removeSpace(PhoneNumberUtil.VALID_PUNCTUATION);
/* A punctuation sequence without white space. */
String nonSpacePunctuation = "[" + nonSpacePunctuationChars + "]" + punctuationLimit;
/* A punctuation sequence allowing white space. */
String punctuation = "[" + PhoneNumberUtil.VALID_PUNCTUATION + "]" + punctuationLimit;
/* A digits block without punctuation. */
String digitSequence = "\\p{Nd}" + limit(1, digitBlockLimit);
/* Punctuation that may be at the start of a phone number - brackets and plus signs. */
String leadClass = "[(\\[" + PhoneNumberUtil.PLUS_CHARS + "]";
/* Phone number pattern allowing optional punctuation. */
PATTERN = Pattern.compile(
"(?:" + leadClass + punctuation + ")" + leadLimit +
digitSequence + "(?:" + punctuation + digitSequence + ")" + blockLimit +
"(?:" + PhoneNumberUtil.KNOWN_EXTN_PATTERNS + ")?",
PhoneNumberUtil.REGEX_FLAGS);
/* Phone number pattern with no whitespace allowed. */
INNER = Pattern.compile(
leadClass + leadLimit +
digitSequence + "(?:" + nonSpacePunctuation + digitSequence + ")" + blockLimit,
PhoneNumberUtil.REGEX_FLAGS);
}
/** Returns a regular expression quantifier with an upper and lower limit. */
private static String limit(int lower, int upper) {
if ((lower < 0) || (upper <= 0) || (upper < lower)) {
throw new IllegalArgumentException();
}
return "{" + lower + "," + upper + "}";
}
/**
* Returns a copy of {@code characters} with any {@linkplain Character#isSpaceChar space}
* characters removed.
*/
private static String removeSpace(String characters) {
StringBuilder builder = new StringBuilder(characters.length());
int i = 0;
while (i < characters.length()) {
int codePoint = characters.codePointAt(i);
if (!Character.isSpaceChar(codePoint)) {
builder.appendCodePoint(codePoint);
}
i += Character.charCount(codePoint);
}
return builder.toString();
}
/** The potential states of a PhoneNumberMatcher. */
private enum State {
NOT_READY, READY, DONE
}
/** The phone number utility. */
private final PhoneNumberUtil util;
/** The text searched for phone numbers. */
private final CharSequence text;
/**
* The region (country) to assume for phone numbers without an international prefix, possibly
* null.
*/
private final String preferredRegion;
/** The degree of validation requested. */
private final Leniency leniency;
/** The maximum number of retries after matching an invalid number. */
private long maxTries;
/** The iteration tristate. */
private State state = State.NOT_READY;
/** The last successful match, null unless in {@link State#READY}. */
private PhoneNumberMatch lastMatch = null;
/** The next index to start searching at. Undefined in {@link State#DONE}. */
private int searchIndex = 0;
/**
* Creates a new instance. See the factory methods in {@link PhoneNumberUtil} on how to obtain a
* new instance.
*
* @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 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
* in it. Must be {@code >= 0}.
*/
PhoneNumberMatcher(PhoneNumberUtil util, CharSequence text, String country, Leniency leniency,
long maxTries) {
if ((util == null) || (leniency == null)) {
throw new NullPointerException();
}
if (maxTries < 0) {
throw new IllegalArgumentException();
}
this.util = util;
this.text = (text != null) ? text : "";
this.preferredRegion = country;
this.leniency = leniency;
this.maxTries = maxTries;
}
@Override
public boolean hasNext() {
if (state == State.NOT_READY) {
lastMatch = find(searchIndex);
if (lastMatch == null) {
state = State.DONE;
} else {
searchIndex = lastMatch.end();
state = State.READY;
}
}
return state == State.READY;
}
@Override
public PhoneNumberMatch next() {
// Check the state and find the next match as a side-effect if necessary.
if (!hasNext()) {
throw new NoSuchElementException();
}
// Don't retain that memory any longer than necessary.
PhoneNumberMatch result = lastMatch;
lastMatch = null;
state = State.NOT_READY;
return result;
}
/**
* Attempts to find the next subsequence in the searched sequence on or after {@code searchIndex}
* that represents a phone number. Returns the next match, null if none was found.
*
* @param index the search index to start searching at
* @return the phone number match found, null if none can be found
*/
private PhoneNumberMatch find(int index) {
Matcher matcher = PATTERN.matcher(text);
while ((maxTries > 0) && matcher.find(index)) {
int start = matcher.start();
CharSequence candidate = text.subSequence(start, matcher.end());
// Check for extra numbers at the end.
// TODO: This is the place to start when trying to support extraction of multiple phone number
// from split notations (+41 79 123 45 67 / 68).
candidate = trimAfterFirstMatch(PhoneNumberUtil.SECOND_NUMBER_START_PATTERN, candidate);
PhoneNumberMatch match = extractMatch(candidate, start);
if (match != null) {
return match;
}
index = start + candidate.length();
maxTries--;
}
return null;
}
/**
* Trims away any characters after the first match of {@code pattern} in {@code candidate},
* returning the trimmed version.
*/
private static CharSequence trimAfterFirstMatch(Pattern pattern, CharSequence candidate) {
Matcher trailingCharsMatcher = pattern.matcher(candidate);
if (trailingCharsMatcher.find()) {
candidate = candidate.subSequence(0, trailingCharsMatcher.start());
}
return candidate;
}
/**
* Attempts to extract a match from a {@code candidate} character sequence.
*
* @param candidate the candidate text that might contain a phone number
* @param offset the offset of {@code candidate} within {@link #text}
* @return the match found, null if none can be found
*/
private PhoneNumberMatch extractMatch(CharSequence candidate, int offset) {
// Skip a match that is more likely a publication page reference.
if (PUB_PAGES.matcher(candidate).find()) {
return null;
}
// Try to come up with a valid match given the entire candidate.
String rawString = candidate.toString();
PhoneNumberMatch match = parseAndVerify(rawString, offset);
if (match != null) {
return match;
}
// If that failed, try to find an inner match without white space.
return extractInnerMatch(rawString, offset);
}
/**
* Attempts to extract a match from {@code candidate} using the {@link #INNER} pattern.
*
* @param candidate the candidate text that might contain a phone number
* @param offset the offset of {@code candidate} within {@link #text}
* @return the match found, null if none can be found
*/
private PhoneNumberMatch extractInnerMatch(String candidate, int offset) {
int index = 0;
Matcher matcher = INNER.matcher(candidate);
while ((maxTries > 0) && matcher.find(index)) {
String innerCandidate = candidate.substring(matcher.start(), matcher.end());
PhoneNumberMatch match = parseAndVerify(innerCandidate, offset + matcher.start());
if (match != null) {
return match;
}
maxTries--;
index = matcher.end();
}
return null;
}
/**
* Parses a phone number from the {@code candidate} using {@link PhoneNumberUtil#parse} and
* verifies it matches the requested {@link #leniency}. If parsing and verification succeed, a
* corresponding {@link PhoneNumberMatch} is returned, otherwise this method returns null.
*
* @param candidate the candidate match
* @param offset the offset of {@code candidate} within {@link #text}
* @return the parsed and validated phone number match, or null
*/
private PhoneNumberMatch parseAndVerify(String candidate, int offset) {
try {
PhoneNumber number = util.parse(candidate, preferredRegion);
if (leniency.verify(number, util)) {
return new PhoneNumberMatch(offset, candidate, number);
}
} catch (NumberParseException e) {
// ignore and continue
}
return null;
}
/**
* Always throws {@link UnsupportedOperationException} as removal is not supported.
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

+ 176
- 59
java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java View File

@ -23,14 +23,15 @@ import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -56,8 +57,6 @@ public class PhoneNumberUtil {
static final int MAX_LENGTH_COUNTRY_CODE = 3;
static final String META_DATA_FILE_PREFIX =
"/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto";
static final String COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME =
"CountryCodeToRegionCodeMap";
private String currentFilePrefix = META_DATA_FILE_PREFIX;
private static final Logger LOGGER = Logger.getLogger(PhoneNumberUtil.class.getName());
@ -379,6 +378,36 @@ public class PhoneNumberUtil {
TOO_LONG,
}
/**
* Leniency when {@linkplain PhoneNumberUtil#findNumbers finding} potential phone numbers in text
* segments.
*/
public enum Leniency {
/**
* Phone numbers accepted are {@linkplain PhoneNumberUtil#isPossibleNumber(PhoneNumber)
* possible}, but not necessarily {@linkplain PhoneNumberUtil#isValidNumber(PhoneNumber) valid}.
*/
POSSIBLE {
@Override
boolean verify(PhoneNumber number, PhoneNumberUtil util) {
return util.isPossibleNumber(number);
}
},
/**
* Phone numbers accepted are {@linkplain PhoneNumberUtil#isPossibleNumber(PhoneNumber)
* possible} and {@linkplain PhoneNumberUtil#isValidNumber(PhoneNumber) valid}.
*/
VALID {
@Override
boolean verify(PhoneNumber number, PhoneNumberUtil util) {
return util.isValidNumber(number);
}
};
/** Returns true if {@code number} is a verified number according to this leniency. */
abstract boolean verify(PhoneNumber number, PhoneNumberUtil util);
}
/**
* This class implements a singleton, so the only constructor is private.
*/
@ -551,7 +580,7 @@ public class PhoneNumberUtil {
* - some geographical numbers have no area codes.
*
* @param number the PhoneNumber object for which clients want to know the length of the area
* code in the national_number field.
* code.
* @return the length of area code of the PhoneNumber object passed in.
*/
public int getLengthOfGeographicalAreaCode(PhoneNumber number) {
@ -829,6 +858,17 @@ public class PhoneNumberUtil {
return formattedNumber.toString();
}
/**
* Formats a phone number in national format for dialing using the carrier as specified in the
* carrierCode. The carrierCode will always be used regardless of whether the phone number already
* has a preferred domestic carrier code stored. If carrierCode contains an empty string, return
* the number in national format without any carrier code.
*
* @param number the phone number to be formatted
* @param carrierCode the carrier selection code to be used
* @return the formatted phone number in national format for dialing using the carrier as
* specified in the carrierCode
*/
public String formatNationalNumberWithCarrierCode(PhoneNumber number, String carrierCode) {
int countryCode = number.getCountryCode();
String nationalSignificantNumber = getNationalSignificantNumber(number);
@ -850,6 +890,29 @@ public class PhoneNumberUtil {
return formattedNumber.toString();
}
/**
* Formats a phone number in national format for dialing using the carrier as specified in the
* preferred_domestic_carrier_code field of the PhoneNumber object passed in. If that is missing,
* use the fallbackCarrierCode passed in instead. If there is no preferred_domestic_carrier_code,
* and the fallbackCarrierCode contains an empty string, return the number in national format
* without any carrier code.
*
* Use formatNationalNumberWithCarrierCode instead if the carrier code passed in should take
* precedence over the number's preferred_domestic_carrier_code when formatting.
*
* @param number the phone number to be formatted
* @param fallbackCarrierCode the carrier selection code to be used, if none is found in the
* phone number itself
* @return the formatted phone number in national format for dialing using the number's
* preferred_domestic_carrier_code, or the fallbackCarrierCode passed in if none is found
*/
public String formatNationalNumberWithPreferredCarrierCode(PhoneNumber number,
String fallbackCarrierCode) {
return formatNationalNumberWithCarrierCode(number, number.hasPreferredDomesticCarrierCode()
? number.getPreferredDomesticCarrierCode()
: fallbackCarrierCode);
}
/**
* Formats a phone number for out-of-country dialing purpose. If no countryCallingFrom
* is supplied, we format the number in its INTERNATIONAL format. If the countryCallingFrom is
@ -1027,7 +1090,7 @@ public class PhoneNumberUtil {
}
// Note that carrierCode is optional - if NULL or an empty string, no carrier code replacement
// will take place. Carrier code replacement occurs before national prefix replacement.
// will take place.
private String formatAccordingToFormats(String nationalNumber,
List<NumberFormat> availableFormats,
PhoneNumberFormat numberFormat,
@ -1038,9 +1101,10 @@ public class PhoneNumberUtil {
// We always use the last leading_digits_pattern, as it is the most detailed.
numFormat.getLeadingDigitsPattern(size - 1)).matcher(nationalNumber).lookingAt()) {
Matcher m = regexCache.getPatternForRegex(numFormat.getPattern()).matcher(nationalNumber);
String numberFormatRule = numFormat.getFormat();
if (m.matches()) {
if (carrierCode != null && carrierCode.length() > 0 &&
String numberFormatRule = numFormat.getFormat();
if (numberFormat == PhoneNumberFormat.NATIONAL &&
carrierCode != null && carrierCode.length() > 0 &&
numFormat.getDomesticCarrierCodeFormattingRule().length() > 0) {
// Replace the $CC in the formatting rule with the desired carrier code.
String carrierCodeFormattingRule = numFormat.getDomesticCarrierCodeFormattingRule();
@ -1050,15 +1114,18 @@ public class PhoneNumberUtil {
// combined in the appropriate way.
numberFormatRule = FIRST_GROUP_PATTERN.matcher(numberFormatRule)
.replaceFirst(carrierCodeFormattingRule);
}
String nationalPrefixFormattingRule = numFormat.getNationalPrefixFormattingRule();
if (numberFormat == PhoneNumberFormat.NATIONAL &&
nationalPrefixFormattingRule != null &&
nationalPrefixFormattingRule.length() > 0) {
Matcher firstGroupMatcher = FIRST_GROUP_PATTERN.matcher(numberFormatRule);
return m.replaceAll(firstGroupMatcher.replaceFirst(nationalPrefixFormattingRule));
} else {
return m.replaceAll(numberFormatRule);
} else {
// Use the national prefix formatting rule instead.
String nationalPrefixFormattingRule = numFormat.getNationalPrefixFormattingRule();
if (numberFormat == PhoneNumberFormat.NATIONAL &&
nationalPrefixFormattingRule != null &&
nationalPrefixFormattingRule.length() > 0) {
Matcher firstGroupMatcher = FIRST_GROUP_PATTERN.matcher(numberFormatRule);
return m.replaceAll(firstGroupMatcher.replaceFirst(nationalPrefixFormattingRule));
} else {
return m.replaceAll(numberFormatRule);
}
}
}
}
@ -1565,15 +1632,15 @@ public class PhoneNumberUtil {
* @param nationalNumber a string buffer to store the national significant number in, in the case
* that a country code was extracted. The number is appended to any existing contents. If no
* country code was extracted, this will be left unchanged.
* @param storeCountryCodeSource true if the country_code_source field of phoneNumber should be
* populated.
* @param keepRawInput true if the country_code_source and preferred_carrier_code fields of
* phoneNumber should be populated.
* @param phoneNumber the PhoneNumber object that needs to be populated with country code and
* country code source. Note the country code is always populated, whereas country code source
* is only populated when keepCountryCodeSource is true.
* @return the country code extracted or 0 if none could be extracted
*/
int maybeExtractCountryCode(String number, PhoneMetadata defaultRegionMetadata,
StringBuffer nationalNumber, boolean storeCountryCodeSource,
StringBuffer nationalNumber, boolean keepRawInput,
PhoneNumber phoneNumber)
throws NumberParseException {
if (number.length() == 0) {
@ -1588,7 +1655,7 @@ public class PhoneNumberUtil {
CountryCodeSource countryCodeSource =
maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix);
if (storeCountryCodeSource) {
if (keepRawInput) {
phoneNumber.setCountryCodeSource(countryCodeSource);
}
if (countryCodeSource != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
@ -1621,11 +1688,7 @@ public class PhoneNumberUtil {
// If so, strip this, and see if the resultant number is valid.
StringBuffer potentialNationalNumber =
new StringBuffer(normalizedNumber.substring(defaultCountryCodeString.length()));
maybeStripNationalPrefix(
potentialNationalNumber,
defaultRegionMetadata.getNationalPrefixForParsing(),
defaultRegionMetadata.getNationalPrefixTransformRule(),
validNumberPattern);
maybeStripNationalPrefixAndCarrierCode(potentialNationalNumber, defaultRegionMetadata);
Matcher possibleNumberMatcher =
regexCache.getPatternForRegex(generalDesc.getPossibleNumberPattern()).matcher(
potentialNationalNumber);
@ -1635,7 +1698,7 @@ public class PhoneNumberUtil {
(possibleNumberMatcher.lookingAt() &&
possibleNumberMatcher.end() != potentialNationalNumber.length())) {
nationalNumber.append(potentialNationalNumber);
if (storeCountryCodeSource) {
if (keepRawInput) {
phoneNumber.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);
}
phoneNumber.setCountryCode(defaultCountryCode);
@ -1718,45 +1781,54 @@ public class PhoneNumberUtil {
*
* @param number the normalized telephone number that we wish to strip any national
* dialing prefix from
* @param possibleNationalPrefix a regex that represents the national direct dialing prefix
* from the country we think this number may be dialed in
* @param transformRule the string that specifies how number should be transformed according
* to the regex specified in possibleNationalPrefix
* @param nationalNumberRule a regular expression that specifies what a valid phonenumber from
* this region should look like after any national prefix was stripped or transformed
* @param metadata the metadata for the country that we think this number is from
* @return the carrier code extracted if it is present, otherwise return an empty string.
*/
void maybeStripNationalPrefix(StringBuffer number, String possibleNationalPrefix,
String transformRule, Pattern nationalNumberRule) {
String maybeStripNationalPrefixAndCarrierCode(StringBuffer number, PhoneMetadata metadata) {
String carrierCode = "";
int numberLength = number.length();
String possibleNationalPrefix = metadata.getNationalPrefixForParsing();
if (numberLength == 0 || possibleNationalPrefix.length() == 0) {
// Early return for numbers of zero length.
return;
return carrierCode;
}
// Attempt to parse the first digits as a national prefix.
Matcher m = regexCache.getPatternForRegex(possibleNationalPrefix).matcher(number);
if (m.lookingAt()) {
// m.group(1) == null implies nothing was captured by the capturing groups in
// possibleNationalPrefix; therefore, no transformation is necessary, and we
// just remove the national prefix.
if (transformRule == null || transformRule.length() == 0 || m.group(1) == null) {
Matcher prefixMatcher = regexCache.getPatternForRegex(possibleNationalPrefix).matcher(number);
if (prefixMatcher.lookingAt()) {
Pattern nationalNumberRule =
regexCache.getPatternForRegex(metadata.getGeneralDesc().getNationalNumberPattern());
// prefixMatcher.group(numOfGroups) == null implies nothing was captured by the capturing
// groups in possibleNationalPrefix; therefore, no transformation is necessary, and we just
// remove the national prefix.
int numOfGroups = prefixMatcher.groupCount();
String transformRule = metadata.getNationalPrefixTransformRule();
if (transformRule == null || transformRule.length() == 0 ||
prefixMatcher.group(numOfGroups) == null) {
// Check that the resultant number is viable. If not, return.
Matcher nationalNumber = nationalNumberRule.matcher(number.substring(m.end()));
Matcher nationalNumber = nationalNumberRule.matcher(number.substring(prefixMatcher.end()));
if (!nationalNumber.matches()) {
return;
return carrierCode;
}
number.delete(0, m.end());
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.
StringBuffer transformedNumber = new StringBuffer(number);
transformedNumber.replace(0, numberLength, m.replaceFirst(transformRule));
transformedNumber.replace(0, numberLength, prefixMatcher.replaceFirst(transformRule));
Matcher nationalNumber = nationalNumberRule.matcher(transformedNumber.toString());
if (!nationalNumber.matches()) {
return;
return carrierCode;
}
if (numOfGroups > 1) {
carrierCode = prefixMatcher.group(1);
}
number.replace(0, number.length(), transformedNumber.toString());
}
}
return carrierCode;
}
/**
@ -1842,11 +1914,11 @@ public class PhoneNumberUtil {
*
* @param numberToParse number that we are attempting to parse. This can contain formatting
* such as +, ( and -, as well as a phone number extension.
* @param defaultCountry the ISO 3166-1 two-letter country code that denotes the
* country that we are expecting the number to be from. This is only used
* if the number being parsed is not written in international format.
* The country code for the number in this case would be stored as that
* of the default country supplied.
* @param defaultCountry the ISO 3166-1 two-letter country code that denotes the country that
* we are expecting the number to be from. This is only used if the
* number being parsed is not written in international format. The
* country code for the number in this case would be stored as that of
* the default country supplied.
* @return a phone number proto buffer filled with the parsed number
* @throws NumberParseException if the string is not considered to be a viable phone number or if
* no default country was supplied
@ -1866,6 +1938,51 @@ public class PhoneNumberUtil {
parseHelper(numberToParse, defaultCountry, true, true, phoneNumber);
}
/**
* Returns an iterable over all {@link PhoneNumberMatch PhoneNumberMatches} in {@code text}. This
* is a shortcut for {@link #findNumbers(CharSequence, String, Leniency, long)
* getMatcher(text, defaultCountry, Leniency.VALID, Long.MAX_VALUE)}.
*
* @param text the text to search for phone numbers, null for no text
* @param defaultCountry the ISO 3166-1 two-letter country code that denotes the country that
* we are expecting the number to be from. This is only used if the
* number being parsed is not written in international format. The
* country code for the number in this case would be stored as that of
* the default country supplied. May be null if only international
* numbers are expected.
*/
public Iterable<PhoneNumberMatch> findNumbers(CharSequence text, String defaultCountry) {
return findNumbers(text, defaultCountry, Leniency.VALID, Long.MAX_VALUE);
}
/**
* Returns an iterable over all {@link PhoneNumberMatch PhoneNumberMatches} in {@code text}.
*
* @param text the text to search for phone numbers, null for no text
* @param defaultCountry the ISO 3166-1 two-letter country code that denotes the country that
* we are expecting the number to be from. This is only used if the
* number being parsed is not written in international format. The
* country code for the number in this case would be stored as that of
* the default country supplied. May be null if only international
* numbers are expected.
* @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 in it. Must be {@code >= 0}.
*/
public Iterable<PhoneNumberMatch> findNumbers(
final CharSequence text, final String defaultCountry, final Leniency leniency,
final long maxTries) {
return new Iterable<PhoneNumberMatch>() {
@Override
public Iterator<PhoneNumberMatch> iterator() {
return new PhoneNumberMatcher(
PhoneNumberUtil.this, text, defaultCountry, leniency, maxTries);
}
};
}
/**
* Parses a string and fills up the phoneNumber. This method is the same as the public
* parse() method, with the exception that it allows the default country to be null, for use by
@ -1933,13 +2050,11 @@ public class PhoneNumberUtil {
"The string supplied is too short to be a phone number.");
}
if (countryMetadata != null) {
Pattern validNumberPattern =
regexCache.getPatternForRegex(countryMetadata.getGeneralDesc()
.getNationalNumberPattern());
maybeStripNationalPrefix(normalizedNationalNumber,
countryMetadata.getNationalPrefixForParsing(),
countryMetadata.getNationalPrefixTransformRule(),
validNumberPattern);
String carrierCode =
maybeStripNationalPrefixAndCarrierCode(normalizedNationalNumber, countryMetadata);
if (keepRawInput) {
phoneNumber.setPreferredDomesticCarrierCode(carrierCode);
}
}
int lengthOfNationalNumber = normalizedNationalNumber.length();
if (lengthOfNationalNumber < MIN_LENGTH_FOR_NSN) {
@ -1983,12 +2098,14 @@ public class PhoneNumberUtil {
firstNumber.mergeFrom(firstNumberIn);
PhoneNumber secondNumber = new PhoneNumber();
secondNumber.mergeFrom(secondNumberIn);
// First clear raw_input and country_code_source field and any empty-string extensions so that
// we can use the PhoneNumber.exactlySameAs() method.
// First clear raw_input, country_code_source and preferred_domestic_carrier_code fields and any
// empty-string extensions so that we can use the proto-buffer equality method.
firstNumber.clearRawInput();
firstNumber.clearCountryCodeSource();
firstNumber.clearPreferredDomesticCarrierCode();
secondNumber.clearRawInput();
secondNumber.clearCountryCodeSource();
secondNumber.clearPreferredDomesticCarrierCode();
if (firstNumber.hasExtension() &&
firstNumber.getExtension().length() == 0) {
firstNumber.clearExtension();


+ 34
- 3
java/src/com/google/i18n/phonenumbers/Phonenumber.java View File

@ -15,7 +15,7 @@
*/
/**
* Definition of the class representing international telephone numbers. This class is hand created
* Definition of the class representing international telephone numbers. This class is hand-created
* based on the class file compiled from phonenumber.proto. Please refer to that file for detailed
* descriptions of the meaning of each field.
*/
@ -141,6 +141,25 @@ public final class Phonenumber {
return this;
}
// optional string preferred_domestic_carrier_code = 7;
private boolean hasPreferredDomesticCarrierCode;
private java.lang.String preferredDomesticCarrierCode_ = "";
public boolean hasPreferredDomesticCarrierCode() { return hasPreferredDomesticCarrierCode; }
public String getPreferredDomesticCarrierCode() { return preferredDomesticCarrierCode_; }
public PhoneNumber setPreferredDomesticCarrierCode(String value) {
if (value == null) {
throw new NullPointerException();
}
hasPreferredDomesticCarrierCode = true;
preferredDomesticCarrierCode_ = value;
return this;
}
public PhoneNumber clearPreferredDomesticCarrierCode() {
hasPreferredDomesticCarrierCode = false;
preferredDomesticCarrierCode_ = "";
return this;
}
public final PhoneNumber clear() {
clearCountryCode();
clearNationalNumber();
@ -148,6 +167,7 @@ public final class Phonenumber {
clearItalianLeadingZero();
clearRawInput();
clearCountryCodeSource();
clearPreferredDomesticCarrierCode();
return this;
}
@ -170,6 +190,9 @@ public final class Phonenumber {
if (other.hasCountryCodeSource()) {
setCountryCodeSource(other.getCountryCodeSource());
}
if (other.hasPreferredDomesticCarrierCode()) {
setPreferredDomesticCarrierCode(other.getPreferredDomesticCarrierCode());
}
return this;
}
@ -182,7 +205,9 @@ public final class Phonenumber {
}
return (countryCode_ == other.countryCode_ && nationalNumber_ == other.nationalNumber_ &&
extension_.equals(other.extension_) && italianLeadingZero_ == other.italianLeadingZero_ &&
rawInput_.equals(other.rawInput_) && countryCodeSource_ == other.countryCodeSource_);
rawInput_.equals(other.rawInput_) && countryCodeSource_ == other.countryCodeSource_ &&
preferredDomesticCarrierCode_.equals(other.preferredDomesticCarrierCode_) &&
hasPreferredDomesticCarrierCode() == other.hasPreferredDomesticCarrierCode());
}
@Override
@ -195,7 +220,7 @@ public final class Phonenumber {
// Simplified rendition of the hashCode function automatically generated from the proto
// compiler with java_generate_equals_and_hash set to true. We are happy with unset values to
// be considered equal to their explicitly-set equivalents, so don't check if any value is
// unknown.
// unknown. The only exception to this is the preferred domestic carrier code.
int hash = 41;
hash = (53 * hash) + getCountryCode();
hash = (53 * hash) + Long.valueOf(getNationalNumber()).hashCode();
@ -203,6 +228,8 @@ public final class Phonenumber {
hash = (53 * hash) + (getItalianLeadingZero() ? 1231 : 1237);
hash = (53 * hash) + getRawInput().hashCode();
hash = (53 * hash) + getCountryCodeSource().hashCode();
hash = (53 * hash) + getPreferredDomesticCarrierCode().hashCode();
hash = (53 * hash) + (hasPreferredDomesticCarrierCode() ? 1231 : 1237);
return hash;
}
@ -220,6 +247,10 @@ public final class Phonenumber {
if (hasCountryCodeSource()) {
outputString.append(" Country Code Source: ").append(countryCodeSource_);
}
if (hasPreferredDomesticCarrierCode()) {
outputString.append(" Preferred Domestic Carrier Code: ").
append(preferredDomesticCarrierCode_);
}
return outputString.toString();
}
}


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


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


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


BIN
java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL 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_DK View File


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


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


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


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


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


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


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


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


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


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


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


+ 38
- 4
java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java View File

@ -19,7 +19,7 @@ package com.google.i18n.phonenumbers;
import junit.framework.TestCase;
/**
* Unit tests for PhoneNumberUtil.java
* Unit tests for AsYouTypeFormatter.java
*
* Note that these tests use the metadata contained in the files with TEST_META_DATA_FILE_PREFIX,
* not the normal metadata files, so should not be used for regression test purposes - these tests
@ -31,7 +31,29 @@ public class AsYouTypeFormatterTest extends TestCase {
private PhoneNumberUtil phoneUtil;
public AsYouTypeFormatterTest() {
phoneUtil = (new PhoneNumberUtilTest()).initilizePhoneUtilForTesting();
phoneUtil = PhoneNumberUtilTest.initializePhoneUtilForTesting();
}
public void testInvalidRegion() {
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'));
assertEquals("+48 88 123 12", formatter.inputDigit('2'));
formatter.clear();
assertEquals("6", formatter.inputDigit('6'));
assertEquals("65", formatter.inputDigit('5'));
assertEquals("650", formatter.inputDigit('0'));
assertEquals("6502", formatter.inputDigit('2'));
assertEquals("65025", formatter.inputDigit('5'));
assertEquals("650253", formatter.inputDigit('3'));
}
public void testAYTFUS() {
@ -374,13 +396,25 @@ public class AsYouTypeFormatterTest extends TestCase {
assertEquals("030 123", formatter.inputDigit('3'));
assertEquals("030 1234", formatter.inputDigit('4'));
// 04134 1234
formatter.clear();
assertEquals("0", formatter.inputDigit('0'));
assertEquals("04", formatter.inputDigit('4'));
assertEquals("041", formatter.inputDigit('1'));
assertEquals("041 3", formatter.inputDigit('3'));
assertEquals("041 34", formatter.inputDigit('4'));
assertEquals("04134 1", formatter.inputDigit('1'));
assertEquals("04134 12", formatter.inputDigit('2'));
assertEquals("04134 123", formatter.inputDigit('3'));
assertEquals("04134 1234", formatter.inputDigit('4'));
// 08021 2345
formatter.clear();
assertEquals("0", formatter.inputDigit('0'));
assertEquals("08", formatter.inputDigit('8'));
assertEquals("080", formatter.inputDigit('0'));
assertEquals("0802", formatter.inputDigit('2'));
assertEquals("08021", formatter.inputDigit('1'));
assertEquals("080 2", formatter.inputDigit('2'));
assertEquals("080 21", formatter.inputDigit('1'));
assertEquals("08021 2", formatter.inputDigit('2'));
assertEquals("08021 23", formatter.inputDigit('3'));
assertEquals("08021 234", formatter.inputDigit('4'));


+ 72
- 0
java/test/com/google/i18n/phonenumbers/PhoneNumberMatchTest.java View File

@ -0,0 +1,72 @@
/*
* 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;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import junit.framework.TestCase;
/**
* Tests for {@link PhoneNumberMatch}.
*
* @author Tom Hofmann
*/
public class PhoneNumberMatchTest extends TestCase {
/**
* Tests the value type semantics. Equality and hash code must be based on the covered range and
* corresponding phone number. Range and number correctness are tested by
* {@link PhoneNumberMatcherTest}.
*/
public void testValueTypeSemantics() throws Exception {
PhoneNumber number = new PhoneNumber();
PhoneNumberMatch match1 = new PhoneNumberMatch(10, "1 800 234 45 67", number);
PhoneNumberMatch match2 = new PhoneNumberMatch(10, "1 800 234 45 67", number);
assertEquals(match1, match2);
assertEquals(match1.hashCode(), match2.hashCode());
assertEquals(match1.start(), match2.start());
assertEquals(match1.end(), match2.end());
assertEquals(match1.number(), match2.number());
assertEquals(match1.rawString(), match2.rawString());
assertEquals("1 800 234 45 67", match1.rawString());
}
/**
* Tests the value type semantics for matches with a null number.
*/
public void testIllegalArguments() throws Exception {
try {
new PhoneNumberMatch(-110, "1 800 234 45 67", new PhoneNumber());
fail();
} catch (IllegalArgumentException e) { /* success */ }
try {
new PhoneNumberMatch(10, "1 800 234 45 67", null);
fail();
} catch (NullPointerException e) { /* success */ }
try {
new PhoneNumberMatch(10, null, new PhoneNumber());
fail();
} catch (NullPointerException e) { /* success */ }
try {
new PhoneNumberMatch(10, null, null);
fail();
} catch (NullPointerException e) { /* success */ }
}
}

+ 540
- 0
java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java View File

@ -0,0 +1,540 @@
/*
* 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;
import com.google.i18n.phonenumbers.PhoneNumberUtil.Leniency;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Tests for {@link PhoneNumberMatcher}. This only tests basic functionality based on test metadata.
* See {@link PhoneNumberMatcherRegressionTest} for regression tests.
*
* @author Tom Hofmann
* @see PhoneNumberUtilTest {@link PhoneNumberUtilTest} for the origin of the test data
*/
public class PhoneNumberMatcherTest extends TestCase {
private PhoneNumberUtil phoneUtil;
@Override
protected void setUp() throws Exception {
phoneUtil = PhoneNumberUtilTest.initializePhoneUtilForTesting();
}
/** See {@link PhoneNumberUtilTest#testParseNationalNumber()}. */
public void testFindNationalNumber() throws Exception {
// same cases as in testParseNationalNumber
doTestFindInContext("033316005", "NZ");
doTestFindInContext("33316005", "NZ");
// National prefix attached and some formatting present.
doTestFindInContext("03-331 6005", "NZ");
doTestFindInContext("03 331 6005", "NZ");
// Testing international prefixes.
// Should strip country code.
doTestFindInContext("0064 3 331 6005", "NZ");
// Try again, but this time we have an international number with Region Code US. It should
// recognize the country code and parse accordingly.
doTestFindInContext("01164 3 331 6005", "US");
doTestFindInContext("+64 3 331 6005", "US");
doTestFindInContext("64(0)64123456", "NZ");
// Check that using a "/" is fine in a phone number.
doTestFindInContext("123/45678", "DE");
doTestFindInContext("123-456-7890", "US");
}
/** See {@link PhoneNumberUtilTest#testParseWithInternationalPrefixes()}. */
public void testFindWithInternationalPrefixes() throws Exception {
doTestFindInContext("+1 (650) 333-6000", "NZ");
doTestFindInContext("1-650-333-6000", "US");
// Calling the US number from Singapore by using different service providers
// 1st test: calling using SingTel IDD service (IDD is 001)
doTestFindInContext("0011-650-333-6000", "SG");
// 2nd test: calling using StarHub IDD service (IDD is 008)
doTestFindInContext("0081-650-333-6000", "SG");
// 3rd test: calling using SingTel V019 service (IDD is 019)
doTestFindInContext("0191-650-333-6000", "SG");
// Calling the US number from Poland
doTestFindInContext("0~01-650-333-6000", "PL");
// Using "++" at the start.
doTestFindInContext("++1 (650) 333-6000", "PL");
// Using a full-width plus sign.
doTestFindInContext("\uFF0B1 (650) 333-6000", "SG");
// The whole number, including punctuation, is here represented in full-width form.
doTestFindInContext("\uFF0B\uFF11\u3000\uFF08\uFF16\uFF15\uFF10\uFF09" +
"\u3000\uFF13\uFF13\uFF13\uFF0D\uFF16\uFF10\uFF10\uFF10",
"SG");
}
/** See {@link PhoneNumberUtilTest#testParseWithLeadingZero()}. */
public void testFindWithLeadingZero() throws Exception {
doTestFindInContext("+39 02-36618 300", "NZ");
doTestFindInContext("02-36618 300", "IT");
doTestFindInContext("312 345 678", "IT");
}
/** See {@link PhoneNumberUtilTest#testParseNationalNumberArgentina()}. */
public void testFindNationalNumberArgentina() throws Exception {
// Test parsing mobile numbers of Argentina.
doTestFindInContext("+54 9 343 555 1212", "AR");
doTestFindInContext("0343 15 555 1212", "AR");
doTestFindInContext("+54 9 3715 65 4320", "AR");
doTestFindInContext("03715 15 65 4320", "AR");
// Test parsing fixed-line numbers of Argentina.
doTestFindInContext("+54 11 3797 0000", "AR");
doTestFindInContext("011 3797 0000", "AR");
doTestFindInContext("+54 3715 65 4321", "AR");
doTestFindInContext("03715 65 4321", "AR");
doTestFindInContext("+54 23 1234 0000", "AR");
doTestFindInContext("023 1234 0000", "AR");
}
/** See {@link PhoneNumberUtilTest#testParseWithXInNumber()}. */
public void testFindWithXInNumber() throws Exception {
doTestFindInContext("(0xx) 123456789", "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
// to 7 digits. This assumption is okay for now as all the countries where a carrier selection
// code is written in the form of xx have a national significant number of length larger than 7.
doTestFindInContext("011xx5481429712", "US");
}
/** See {@link PhoneNumberUtilTest#testParseNumbersMexico()}. */
public void testFindNumbersMexico() throws Exception {
// Test parsing fixed-line numbers of Mexico.
doTestFindInContext("+52 (449)978-0001", "MX");
doTestFindInContext("01 (449)978-0001", "MX");
doTestFindInContext("(449)978-0001", "MX");
// Test parsing mobile numbers of Mexico.
doTestFindInContext("+52 1 33 1234-5678", "MX");
doTestFindInContext("044 (33) 1234-5678", "MX");
doTestFindInContext("045 33 1234-5678", "MX");
}
/** See {@link PhoneNumberUtilTest#testParseNumbersWithPlusWithNoRegion()}. */
public void testFindNumbersWithPlusWithNoRegion() throws Exception {
// "ZZ" is allowed only if the number starts with a '+' - then the country code can be
// calculated.
doTestFindInContext("+64 3 331 6005", "ZZ");
// Null is also allowed for the region code in these cases.
doTestFindInContext("+64 3 331 6005", null);
}
/** See {@link PhoneNumberUtilTest#testParseExtensions()}. */
public void testFindExtensions() throws Exception {
doTestFindInContext("03 331 6005 ext 3456", "NZ");
doTestFindInContext("03-3316005x3456", "NZ");
doTestFindInContext("03-3316005 int.3456", "NZ");
doTestFindInContext("03 3316005 #3456", "NZ");
doTestFindInContext("0~0 1800 7493 524", "PL");
doTestFindInContext("(1800) 7493.524", "US");
// Check that the last instance of an extension token is matched.
doTestFindInContext("0~0 1800 7493 524 ~1234", "PL");
// Verifying bug-fix where the last digit of a number was previously omitted if it was a 0 when
// extracting the extension. Also verifying a few different cases of extensions.
doTestFindInContext("+44 2034567890x456", "NZ");
doTestFindInContext("+44 2034567890x456", "GB");
doTestFindInContext("+44 2034567890 x456", "GB");
doTestFindInContext("+44 2034567890 X456", "GB");
doTestFindInContext("+44 2034567890 X 456", "GB");
doTestFindInContext("+44 2034567890 X 456", "GB");
doTestFindInContext("+44 2034567890 X 456", "GB");
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");
doTestFindInContext("(800) 901-3355 ext: 7246433", "US");
}
public void testFindInterspersedWithSpace() throws Exception {
doTestFindInContext("0 3 3 3 1 6 0 0 5", "NZ");
}
/**
* Test matching behavior when starting in the middle of a phone number.
*/
public void testIntermediateParsePositions() throws Exception {
String text = "Call 033316005 or 032316005!";
// | | | | | |
// 0 5 10 15 20 25
// Iterate over all possible indices.
for (int i = 0; i <= 5; i++) {
assertEqualRange(text, i, 5, 14);
}
// 7 and 8 digits in a row are still parsed as number.
assertEqualRange(text, 6, 6, 14);
assertEqualRange(text, 7, 7, 14);
// Anything smaller is skipped to the second instance.
for (int i = 8; i <= 19; i++) {
assertEqualRange(text, i, 19, 28);
}
}
public void testNoMatchIfRegionIsNull() throws Exception {
// Fail on non-international prefix if region code is null.
assertTrue(hasNoMatches(phoneUtil.findNumbers(
"Random text body - number is 0331 6005, see you there", null)));
}
public void testNoMatchInEmptyString() throws Exception {
assertTrue(hasNoMatches(phoneUtil.findNumbers("", "US")));
assertTrue(hasNoMatches(phoneUtil.findNumbers(" ", "US")));
}
public void testNoMatchIfNoNumber() throws Exception {
assertTrue(hasNoMatches(phoneUtil.findNumbers(
"Random text body - number is foobar, see you there", "US")));
}
public void testSequences() throws Exception {
// Test multiple occurrences.
String text = "Call 033316005 or 032316005!";
String region = "NZ";
PhoneNumber number1 = new PhoneNumber();
number1.setCountryCode(phoneUtil.getCountryCodeForRegion(region));
number1.setNationalNumber(33316005);
PhoneNumberMatch match1 = new PhoneNumberMatch(5, "033316005", number1);
PhoneNumber number2 = new PhoneNumber();
number2.setCountryCode(phoneUtil.getCountryCodeForRegion(region));
number2.setNationalNumber(32316005);
PhoneNumberMatch match2 = new PhoneNumberMatch(19, "032316005", number2);
Iterator<PhoneNumberMatch> matches =
phoneUtil.findNumbers(text, region, Leniency.POSSIBLE, Long.MAX_VALUE).iterator();
assertEquals(match1, matches.next());
assertEquals(match2, matches.next());
}
public void testNullInput() throws Exception {
assertTrue(hasNoMatches(phoneUtil.findNumbers(null, "US")));
assertTrue(hasNoMatches(phoneUtil.findNumbers(null, null)));
}
public void testMaxMatches() throws Exception {
// Set up text with 100 valid phone numbers.
StringBuilder numbers = new StringBuilder();
for (int i = 0; i < 100; i++) {
numbers.append("My info: 415-666-7777,");
}
// Matches all 100. Max only applies to failed cases.
List<PhoneNumber> expected = new ArrayList<PhoneNumber>(100);
PhoneNumber number = phoneUtil.parse("+14156667777", null);
for (int i = 0; i < 100; i++) {
expected.add(number);
}
Iterable<PhoneNumberMatch> iterable =
phoneUtil.findNumbers(numbers.toString(), "US", Leniency.VALID, 10);
List<PhoneNumber> actual = new ArrayList<PhoneNumber>(100);
for (PhoneNumberMatch match : iterable) {
actual.add(match.number());
}
assertEquals(expected, actual);
}
public void testMaxMatchesInvalid() throws Exception {
// Set up text with 10 invalid phone numbers followed by 100 valid.
StringBuilder numbers = new StringBuilder();
for (int i = 0; i < 10; i++) {
numbers.append("My address 949-8945-0");
}
for (int i = 0; i < 100; i++) {
numbers.append("My info: 415-666-7777,");
}
Iterable<PhoneNumberMatch> iterable =
phoneUtil.findNumbers(numbers.toString(), "US", Leniency.VALID, 10);
assertFalse(iterable.iterator().hasNext());
}
public void testMaxMatchesMixed() throws Exception {
// Set up text with 100 valid numbers inside an invalid number.
StringBuilder numbers = new StringBuilder();
for (int i = 0; i < 100; i++) {
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.
List<PhoneNumber> expected = new ArrayList<PhoneNumber>(100);
PhoneNumber number = phoneUtil.parse("+14156667777", null);
for (int i = 0; i < 5; i++) {
expected.add(number);
}
Iterable<PhoneNumberMatch> iterable =
phoneUtil.findNumbers(numbers.toString(), "US", Leniency.VALID, 10);
List<PhoneNumber> actual = new ArrayList<PhoneNumber>(100);
for (PhoneNumberMatch match : iterable) {
actual.add(match.number());
}
assertEquals(expected, actual);
}
public void testEmptyIteration() throws Exception {
Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("", "ZZ");
Iterator<PhoneNumberMatch> iterator = iterable.iterator();
assertFalse(iterator.hasNext());
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Violation of the Iterator contract.");
} catch (NoSuchElementException e) { /* Success */ }
assertFalse(iterator.hasNext());
}
public void testSingleIteration() throws Exception {
Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("+14156667777", "ZZ");
// With hasNext() -> next().
Iterator<PhoneNumberMatch> iterator = iterable.iterator();
// Double hasNext() to ensure it does not advance.
assertTrue(iterator.hasNext());
assertTrue(iterator.hasNext());
assertNotNull(iterator.next());
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Violation of the Iterator contract.");
} catch (NoSuchElementException e) { /* Success */ }
assertFalse(iterator.hasNext());
// With next() only.
iterator = iterable.iterator();
assertNotNull(iterator.next());
try {
iterator.next();
fail("Violation of the Iterator contract.");
} catch (NoSuchElementException e) { /* Success */ }
}
public void testDoubleIteration() throws Exception {
Iterable<PhoneNumberMatch> iterable =
phoneUtil.findNumbers("+14156667777 foobar +14156667777 ", "ZZ");
// With hasNext() -> next().
Iterator<PhoneNumberMatch> iterator = iterable.iterator();
// Double hasNext() to ensure it does not advance.
assertTrue(iterator.hasNext());
assertTrue(iterator.hasNext());
assertNotNull(iterator.next());
assertTrue(iterator.hasNext());
assertTrue(iterator.hasNext());
assertNotNull(iterator.next());
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Violation of the Iterator contract.");
} catch (NoSuchElementException e) { /* Success */ }
assertFalse(iterator.hasNext());
// With next() only.
iterator = iterable.iterator();
assertNotNull(iterator.next());
assertNotNull(iterator.next());
try {
iterator.next();
fail("Violation of the Iterator contract.");
} catch (NoSuchElementException e) { /* Success */ }
}
/**
* Ensures that {@link Iterator#remove()} is not supported and that calling it does not
* change iteration behavior.
*/
public void testRemovalNotSupported() throws Exception {
Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("+14156667777", "ZZ");
Iterator<PhoneNumberMatch> iterator = iterable.iterator();
try {
iterator.remove();
fail("Iterator must not support remove.");
} catch (UnsupportedOperationException e) { /* success */ }
assertTrue(iterator.hasNext());
try {
iterator.remove();
fail("Iterator must not support remove.");
} catch (UnsupportedOperationException e) { /* success */ }
assertNotNull(iterator.next());
try {
iterator.remove();
fail("Iterator must not support remove.");
} catch (UnsupportedOperationException e) { /* success */ }
assertFalse(iterator.hasNext());
}
/**
* Asserts that another number can be found in {@code text} starting at {@code index}, and that
* its corresponding range is {@code [start, end)}.
*/
private void assertEqualRange(CharSequence text, int index, int start, int end) {
CharSequence sub = text.subSequence(index, text.length());
Iterator<PhoneNumberMatch> matches =
phoneUtil.findNumbers(sub, "NZ", Leniency.POSSIBLE, Long.MAX_VALUE).iterator();
assertTrue(matches.hasNext());
PhoneNumberMatch match = matches.next();
assertEquals(start - index, match.start());
assertEquals(end - index, match.end());
assertEquals(match.rawString(), sub.subSequence(match.start(), match.end()).toString());
}
/**
* Tests numbers found by {@link PhoneNumberUtil#find(CharSequence, String)} in various
* textual contexts.
*
* @param number the number to test and the corresponding region code to use
*/
private void doTestFindInContext(String number, String defaultCountry) throws Exception {
findPossibleInContext(number, defaultCountry);
PhoneNumber parsed = phoneUtil.parse(number, defaultCountry);
if (phoneUtil.isValidNumber(parsed)) {
findValidInContext(number, defaultCountry);
}
}
private void findPossibleInContext(String number, String defaultCountry) {
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(15);
contextPairs.add(new NumberContext("", "")); // no context
contextPairs.add(new NumberContext(" ", "\t")); // whitespace only
contextPairs.add(new NumberContext("Hello ", "")); // no context at end
contextPairs.add(new NumberContext("", " to call me!")); // no context at start
contextPairs.add(new NumberContext("Hi there, call ", " to reach me!")); // no context at start
contextPairs.add(new NumberContext("Hi there, call ", ", or don't")); // with commas
// Three examples without whitespace around the number.
contextPairs.add(new NumberContext("Hi call", ""));
contextPairs.add(new NumberContext("", "forme"));
contextPairs.add(new NumberContext("Hi call", "forme"));
// With other small numbers.
contextPairs.add(new NumberContext("It's cheap! Call ", " before 6:30"));
// With a second number later.
contextPairs.add(new NumberContext("Call ", " or +1800-123-4567!"));
contextPairs.add(new NumberContext("Call me on June 21 at", "")); // with a Month-Day date
// With publication pages.
contextPairs.add(new NumberContext(
"As quoted by Alfonso 12-15 (2009), you may call me at ", ""));
contextPairs.add(new NumberContext(
"As quoted by Alfonso et al. 12-15 (2009), you may call me at ", ""));
// 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);
}
/**
* Tests valid numbers in contexts that fail for {@link Leniency#POSSIBLE}.
*/
private void findValidInContext(String number, String defaultCountry) {
ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(5);
// 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
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);
}
private void doTestInContext(String number, String defaultCountry,
List<NumberContext> contextPairs, Leniency leniency) {
for (NumberContext context : contextPairs) {
String prefix = context.leadingText;
String text = prefix + number + context.trailingText;
int start = prefix.length();
int end = start + number.length();
Iterable<PhoneNumberMatch> iterable =
phoneUtil.findNumbers(text, defaultCountry, leniency, Long.MAX_VALUE);
PhoneNumberMatch match = iterable.iterator().hasNext() ? iterable.iterator().next() : null;
assertNotNull("Did not find a number in '" + text + "'; expected '" + number + "'", match);
CharSequence extracted = text.subSequence(match.start(), match.end());
assertTrue("Unexpected phone region in '" + text + "'; extracted '" + extracted + "'",
start == match.start() && end == match.end());
assertTrue(number.contentEquals(extracted));
assertTrue(match.rawString().contentEquals(extracted));
ensureTermination(text, defaultCountry, leniency);
}
}
/**
* Exhaustively searches for phone numbers from each index within {@code text} to test that
* finding matches always terminates.
*/
private void ensureTermination(String text, String defaultCountry, Leniency leniency) {
for (int index = 0; index <= text.length(); index++) {
String sub = text.substring(index);
StringBuffer matches = new StringBuffer();
// Iterates over all matches.
for (PhoneNumberMatch match :
phoneUtil.findNumbers(sub, defaultCountry, leniency, Long.MAX_VALUE)) {
matches.append(", ").append(match.toString());
}
}
}
/**
* Returns true if there were no matches found.
*/
private boolean hasNoMatches(Iterable<PhoneNumberMatch> iterable) {
return !iterable.iterator().hasNext();
}
/**
* Small class that holds the context of the number we are testing against. The test will
* insert the phone number to be found between leadingText and trailingText.
*/
private class NumberContext {
final String leadingText;
final String trailingText;
NumberContext(String leadingText, String trailingText) {
this.leadingText = leadingText;
this.trailingText = trailingText;
}
}
}

+ 402
- 504
java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
File diff suppressed because it is too large
View File


+ 30
- 4
java/test/com/google/i18n/phonenumbers/PhonenumberTest.java View File

@ -28,7 +28,7 @@ import junit.framework.TestCase;
*/
public class PhonenumberTest extends TestCase {
public void testEqualsSimpleNumber() throws Exception {
public void testEqualSimpleNumber() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setCountryCode(1).setNationalNumber(6502530000L);
@ -39,7 +39,7 @@ public class PhonenumberTest extends TestCase {
assertEquals(numberA.hashCode(), numberB.hashCode());
}
public void testEqualsWithOtherFields() throws Exception {
public void testEqualWithItalianLeadingZeroSetToDefault() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setCountryCode(1).setNationalNumber(6502530000L).setItalianLeadingZero(false);
@ -49,16 +49,20 @@ public class PhonenumberTest extends TestCase {
// These should still be equal, since the default value for this field is false.
assertEquals(numberA, numberB);
assertEquals(numberA.hashCode(), numberB.hashCode());
}
public void testEqualWithCountryCodeSourceSet() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setRawInput("+1 650 253 00 00").
setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN);
PhoneNumber numberB = new PhoneNumber();
numberB.setRawInput("+1 650 253 00 00").
setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN);
assertEquals(numberA, numberB);
assertEquals(numberA.hashCode(), numberB.hashCode());
}
public void testNonEqualWithOtherFields() throws Exception {
public void testNonEqualWithItalianLeadingZeroSetToTrue() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setCountryCode(1).setNationalNumber(6502530000L).setItalianLeadingZero(true);
@ -69,7 +73,7 @@ public class PhonenumberTest extends TestCase {
assertFalse(numberA.hashCode() == numberB.hashCode());
}
public void testNonEqualWithAllFields() throws Exception {
public void testNonEqualWithDifferingRawInput() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setCountryCode(1).setNationalNumber(6502530000L).setRawInput("+1 650 253 00 00").
setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN);
@ -83,4 +87,26 @@ public class PhonenumberTest extends TestCase {
assertFalse(numberA.equals(numberB));
assertFalse(numberA.hashCode() == numberB.hashCode());
}
public void testNonEqualWithPreferredDomesticCarrierCodeSetToDefault() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setCountryCode(1).setNationalNumber(6502530000L).setPreferredDomesticCarrierCode("");
PhoneNumber numberB = new PhoneNumber();
numberB.setCountryCode(1).setNationalNumber(6502530000L);
assertFalse(numberA.equals(numberB));
assertFalse(numberA.hashCode() == numberB.hashCode());
}
public void testEqualWithPreferredDomesticCarrierCodeSetToDefault() throws Exception {
PhoneNumber numberA = new PhoneNumber();
numberA.setCountryCode(1).setNationalNumber(6502530000L).setPreferredDomesticCarrierCode("");
PhoneNumber numberB = new PhoneNumber();
numberB.setCountryCode(1).setNationalNumber(6502530000L).setPreferredDomesticCarrierCode("");
assertEquals(numberA, numberB);
assertEquals(numberA.hashCode(), numberB.hashCode());
}
}

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


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


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


Loading…
Cancel
Save