diff --git a/java/build.xml b/java/build.xml index 01740a290..d1768e66f 100644 --- a/java/build.xml +++ b/java/build.xml @@ -25,7 +25,7 @@ - + diff --git a/java/release_notes.txt b/java/release_notes.txt index c26cb85b9..187378642 100644 --- a/java/release_notes.txt +++ b/java/release_notes.txt @@ -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 diff --git a/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java b/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java index 87a8d0544..04f7849d1 100644 --- a/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java +++ b/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java @@ -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")); } diff --git a/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java b/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java index 6fa912455..224660670 100644 --- a/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java +++ b/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java @@ -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 { " " + PhoneNumberUtil.META_DATA_FILE_PREFIX + "_*\n" + "Mapping file will be stored in:\n" + " /" + 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"; diff --git a/java/resources/com/google/i18n/phonenumbers/proto/phonenumber.proto b/java/resources/com/google/i18n/phonenumbers/proto/phonenumber.proto index 1628cc371..8a5e7ab26 100644 --- a/java/resources/com/google/i18n/phonenumbers/proto/phonenumber.proto +++ b/java/resources/com/google/i18n/phonenumbers/proto/phonenumber.proto @@ -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 diff --git a/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml b/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml index 70f66dbb3..86ea951a0 100644 --- a/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml +++ b/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml @@ -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} 70123456 @@ -2265,7 +2266,8 @@ + nationalPrefixForParsing="0(1\d)?" + carrierCodeFormattingRule="$NP$CC $FG"> [234] @@ -2316,8 +2318,8 @@ + nationalPrefixForParsing="0(?:(1[245]|2[135]|[34]1)(\d{10}))?" + nationalPrefixTransformRule="$2"> + - + 2 $1 $2 $3 - + [357]| 4[1-35]| @@ -3668,9 +3673,10 @@ + nationalPrefix="0" nationalPrefixForParsing="0([3579]|4(?:44|56))"> - + 1(?: 8[2-9]| @@ -3690,7 +3696,7 @@ )| [24-8] - ($1) $2 + $1 $2 @@ -3714,7 +3720,7 @@ $1 $2 - + 3 $1 $2 @@ -3803,11 +3809,12 @@ - + - 2| + [24]| 8[389] $1 $2 @@ -3818,7 +3825,7 @@ - [289]\d{7,9} + [2489]\d{7,9} \d{8,10} @@ -3832,16 +3839,40 @@ 83123456 - 800\d{7} - \d{10} - 8001234567 + 800\d{7} + \d{10} + 8001234567 - - 90[059]\d{7} - \d{10} - 9001234567 + + 90[059]\d{7} + \d{10} + 9001234567 + + 4000\d{4} + \d{8} + 40001234 + + + + + 1(?: + 02[2-469]| + 1(?: + 1[0235-9]| + 2| + 37| + 46| + 75| + 8[79]| + 9[0-379] + )| + 212) + + \d{3,4} + 1022 + @@ -4348,7 +4379,8 @@ 4[0-2]| 5[0-3]| 6[01]| - 72| + 7[12]| + 81| 99 )\d{6} @@ -5207,7 +5239,12 @@ + nationalPrefixForParsing="(10(?:01|[12]0|88))" + carrierCodeFormattingRule="$CC $FG"> + + + $1 + [2-9]\d{5} \d{6} @@ -6113,25 +6150,22 @@ - + 32 $1 $2 $3 $4 - + - 2| - 3[13-79]| - 446 + [24]| + 3[13-79] - $1 $2 - - - 44[2-5] - $1 $2 + $1 $2 $3 $4 - + [5679] - $1 $2 $3 + $1 $2 $3 $4 8 @@ -6140,33 +6174,46 @@ - [1-3579]\d{7}| + [1-579]\d{7}| 8\d{8} - \d{3,9} + \d{5,9} - + (?: 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} - \d{3,8} + \d{5,8} 32123456 @@ -10264,8 +10311,8 @@ nationalPrefix 1[4-6]XX-YYYY - Country-wide common number services, display as it is without hyphens --> + nationalPrefix="0" nationalPrefixForParsing="0(8[1-46-8]|85\d{2})?" + nationalPrefixFormattingRule="$NP$FG" carrierCodeFormattingRule="$NP$CC-$FG"> @@ -10540,7 +10587,7 @@ 5\d )| 6(?: - 0[03679]| + 0[034679]| 5[015-9]| 6\d| 7[067] @@ -10811,12 +10858,8 @@ - - 20[23] - $1 $2 $3 $4 - - - 20[579] + + 20 $1 $2 $3 $4 @@ -10844,14 +10887,14 @@ 20(?: - [23]| + 2[23]| 5[4-6]| 77| 9[89] )\d{6} - \d{9,10} - 202345678 + \d{10} + 2023123456 @@ -11316,7 +11359,8 @@ + nationalPrefixForParsing="(15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\d)" + carrierCodeFormattingRule="$CC $FG"> @@ -12517,7 +12561,8 @@ - + @@ -12718,8 +12763,8 @@ nationalPrefixFormattingRule="$NP $FG"> + 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. --> @@ -15184,13 +15229,13 @@ 800000 - (?: - 4?4[1-3]| - 6?47 + 44[1-3]| + 647 )\d{4} + \d{7} 4410123 @@ -15586,8 +15631,41 @@ - + + + + + [2-9]\d{3} + \d{4} + + + + (?: + [2-468]\d| + 7[01] + )\d{2} + + + 2158 + + + NA + NA + + + + (?: + [59]\d| + 7[2-9] + )\d{2} + + 5012 + + + 1\d{2,3} + \d{3,4} + @@ -16565,36 +16643,98 @@ - + - + + + [23]| + 4(?: + [0-35-9]| + 4[0-35-9] + ) + + $1 $2 $3 + + + [589] + $1 $2 $3 + + + 444 $1 $2 $3 - [2-589]\d{9} - \d{10} + + [2-589]\d{9}| + 444\d{4} + + \d{7,10} + - [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} + \d{10} 2123456789 - 5\d{9} - 5123456789 + + 5(?: + 0[1-35-7]| + 22| + 3\d| + 4[1-79]| + 5[1-5]| + 9[246] + )\d{7} + + \d{10} + 5012345678 + + 512\d{7} + \d{10} + 5123456789 + 800\d{7} + \d{10} 8001234567 900\d{7} + \d{10} 9001234567 + + + + 444\d{4}| + 850\d{7} + + \d{7,10} + 4441444 + @@ -17458,8 +17598,9 @@ + nationalPrefix="0" nationalPrefixForParsing="(1\d{2})|0" + nationalPrefixFormattingRule="$NP$FG" + carrierCodeFormattingRule="$CC $FG"> $1-$2 diff --git a/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml b/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml index c2b0dd84d..c01acf742 100644 --- a/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml +++ b/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml @@ -72,7 +72,7 @@ $1 15 $2-$3 + carrierCodeFormattingRule="$NP$FG $CC"> 9(?:1[02-9]|[23]) $1 $2-$3 @@ -196,6 +196,14 @@ [34]0|[68]9 $1 $2 + + + [4-9] + [4-6]|[7-9](?:\d[1-9]|[1-9]\d) + $1 $2 + [4-9] [4-6]|[7-9](?:\d[1-9]|[1-9]\d) @@ -372,7 +380,7 @@ nationalPrefix 1[4-6]XX-YYYY - Country-wide common number services, display as it is without hyphens --> diff --git a/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java b/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java index b0e500de8..11b8ba08f 100644 --- a/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java +++ b/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java @@ -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(); } } diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java new file mode 100644 index 000000000..b00a0e18d --- /dev/null +++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java @@ -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}. + * + *

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. + * + *

The following annotated example clarifies the relationship between the searched text, the + * match offsets, and the parsed number: + + *

+ * 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());
+ * 
+ * + * @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; + } +} diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java new file mode 100644 index 000000000..5121c259f --- /dev/null +++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java @@ -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}. + * + *

Vanity numbers (phone numbers using alphabetic digits such as 1-800-SIX-FLAGS are + * not found. + * + *

This class is not thread-safe. + * + * @author Tom Hofmann + */ +final class PhoneNumberMatcher implements Iterator { + /** + * The phone number pattern used by {@link #find}, similar to + * {@code PhoneNumberUtil.VALID_PHONE_NUMBER}, but with the following differences: + *

    + *
  • All captures are limited in order to place an upper bound to the text matched by the + * pattern. + *
      + *
    • Leading punctuation / plus signs are limited. + *
    • Consecutive occurrences of punctuation are limited. + *
    • Number of digits is limited. + *
    + *
  • No whitespace is allowed at the start or end. + *
  • No alpha digits (vanity numbers such as 1-800-SIX-FLAGS) are currently supported. + *
+ */ + 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: + *
Computing Complete Answers to Queries in the Presence of Limited Access Patterns.
+   * Chen Li. VLDB J. 12(3): 211-227 (2003).
+ * + * 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(); + } +} diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java index 9f0039203..df486bc80 100644 --- a/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java +++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java @@ -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 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 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 findNumbers( + final CharSequence text, final String defaultCountry, final Leniency leniency, + final long maxTries) { + + return new Iterable() { + @Override + public Iterator 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(); diff --git a/java/src/com/google/i18n/phonenumbers/Phonenumber.java b/java/src/com/google/i18n/phonenumbers/Phonenumber.java index 608a3b40b..89382a1f1 100644 --- a/java/src/com/google/i18n/phonenumbers/Phonenumber.java +++ b/java/src/com/google/i18n/phonenumbers/Phonenumber.java @@ -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(); } } diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF index 1fc75cc6d..cd1431f15 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BF differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO index 191b55c7a..0395299f0 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR index 7fc1610f8..67fe311df 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL index e6bb05edf..2e9c0d8d6 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO index 84926a14e..e89645ad2 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR index 1b3a91c7a..979fa17cd 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK index 84b40d6e4..2b9a712c2 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FO b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FO index f75752f4b..2d2ff1824 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FO and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FO differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GE b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GE index fd894c53f..22f3da287 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GE and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GE differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KR b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KR index 641ce7fce..e66f15795 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KR and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KR differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KW b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KW index 3d5cd85f9..7ab6e3630 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KW and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KW differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LA b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LA index e0c1ec1ca..984aeba23 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LA and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LA differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LU b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LU index e3b025ff1..1b3137441 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LU and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LU differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MU b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MU index 782c700fd..a65c8e327 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MU and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MU differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC index d8528060a..3d74da04d 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SH b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SH index 96e6c73e7..c52c527ea 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SH and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SH differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR index 595e26cd3..0d20cbb59 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR differ diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE index 3cc77d15a..96b653fcb 100644 Binary files a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE and b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE differ diff --git a/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java b/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java index 52db2a751..4920676a7 100644 --- a/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java +++ b/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java @@ -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')); diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberMatchTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberMatchTest.java new file mode 100644 index 000000000..6e1948785 --- /dev/null +++ b/java/test/com/google/i18n/phonenumbers/PhoneNumberMatchTest.java @@ -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 */ } + } +} diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java new file mode 100644 index 000000000..d0485f8d3 --- /dev/null +++ b/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java @@ -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 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 expected = new ArrayList(100); + PhoneNumber number = phoneUtil.parse("+14156667777", null); + for (int i = 0; i < 100; i++) { + expected.add(number); + } + + Iterable iterable = + phoneUtil.findNumbers(numbers.toString(), "US", Leniency.VALID, 10); + List actual = new ArrayList(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 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 expected = new ArrayList(100); + PhoneNumber number = phoneUtil.parse("+14156667777", null); + for (int i = 0; i < 5; i++) { + expected.add(number); + } + + Iterable iterable = + phoneUtil.findNumbers(numbers.toString(), "US", Leniency.VALID, 10); + List actual = new ArrayList(100); + for (PhoneNumberMatch match : iterable) { + actual.add(match.number()); + } + assertEquals(expected, actual); + } + + public void testEmptyIteration() throws Exception { + Iterable iterable = phoneUtil.findNumbers("", "ZZ"); + Iterator 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 iterable = phoneUtil.findNumbers("+14156667777", "ZZ"); + + // With hasNext() -> next(). + Iterator 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 iterable = + phoneUtil.findNumbers("+14156667777 foobar +14156667777 ", "ZZ"); + + // With hasNext() -> next(). + Iterator 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 iterable = phoneUtil.findNumbers("+14156667777", "ZZ"); + + Iterator 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 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 contextPairs = new ArrayList(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 contextPairs = new ArrayList(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 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 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 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; + } + } +} diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java index ffd6017b6..22941bca3 100644 --- a/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java +++ b/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java @@ -16,17 +16,17 @@ package com.google.i18n.phonenumbers; +import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat; import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; -import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; import junit.framework.TestCase; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; /** * Unit tests for PhoneNumberUtil.java @@ -40,31 +40,67 @@ import java.util.regex.Pattern; */ public class PhoneNumberUtilTest extends TestCase { private PhoneNumberUtil phoneUtil; + // This is used by BuildMetadataProtoFromXml. static final String TEST_META_DATA_FILE_PREFIX = "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting"; - static final String TEST_COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME = - "CountryCodeToRegionCodeMapForTesting"; + + // Set up some test numbers to re-use. + private static final PhoneNumber ALPHA_NUMERIC_NUMBER = + new PhoneNumber().setCountryCode(1).setNationalNumber(80074935247L); + private static final PhoneNumber AR_MOBILE = + new PhoneNumber().setCountryCode(54).setNationalNumber(91187654321L); + private static final PhoneNumber AR_NUMBER = + new PhoneNumber().setCountryCode(54).setNationalNumber(1187654321); + private static final PhoneNumber AU_NUMBER = + new PhoneNumber().setCountryCode(61).setNationalNumber(236618300L); + private static final PhoneNumber BS_MOBILE = + new PhoneNumber().setCountryCode(1).setNationalNumber(2423570000L); + private static final PhoneNumber BS_NUMBER = + new PhoneNumber().setCountryCode(1).setNationalNumber(2423651234L); + // Note that this is the same as the example number for DE in the metadata. + private static final PhoneNumber DE_NUMBER = + new PhoneNumber().setCountryCode(49).setNationalNumber(30123456L); + private static final PhoneNumber DE_SHORT_NUMBER = + new PhoneNumber().setCountryCode(49).setNationalNumber(1234L); + private static final PhoneNumber GB_MOBILE = + new PhoneNumber().setCountryCode(44).setNationalNumber(7912345678L); + private static final PhoneNumber GB_NUMBER = + new PhoneNumber().setCountryCode(44).setNationalNumber(2070313000L); + private static final PhoneNumber IT_MOBILE = + new PhoneNumber().setCountryCode(39).setNationalNumber(345678901L); + private static final PhoneNumber IT_NUMBER = + new PhoneNumber().setCountryCode(39).setNationalNumber(236618300L). + setItalianLeadingZero(true); + private static final PhoneNumber NZ_NUMBER = + new PhoneNumber().setCountryCode(64).setNationalNumber(33316005L); + private static final PhoneNumber SG_NUMBER = + new PhoneNumber().setCountryCode(65).setNationalNumber(65218000L); + // A too-long and hence invalid US number. + private static final PhoneNumber US_LONG_NUMBER = + new PhoneNumber().setCountryCode(1).setNationalNumber(65025300001L); + private static final PhoneNumber US_NUMBER = + new PhoneNumber().setCountryCode(1).setNationalNumber(6502530000L); + private static final PhoneNumber US_PREMIUM = + new PhoneNumber().setCountryCode(1).setNationalNumber(9002530000L); + // Too short, but still possible US numbers. + private static final PhoneNumber US_LOCAL_NUMBER = + new PhoneNumber().setCountryCode(1).setNationalNumber(2530000L); + private static final PhoneNumber US_SHORT_BY_ONE_NUMBER = + new PhoneNumber().setCountryCode(1).setNationalNumber(650253000L); + private static final PhoneNumber US_TOLLFREE = + new PhoneNumber().setCountryCode(1).setNationalNumber(8002530000L); public PhoneNumberUtilTest() { - phoneUtil = initilizePhoneUtilForTesting(); + phoneUtil = initializePhoneUtilForTesting(); } - PhoneNumberUtil initilizePhoneUtilForTesting() { + static PhoneNumberUtil initializePhoneUtilForTesting() { PhoneNumberUtil.resetInstance(); - return PhoneNumberUtil.getInstance(TEST_META_DATA_FILE_PREFIX, + return PhoneNumberUtil.getInstance( + TEST_META_DATA_FILE_PREFIX, CountryCodeToRegionCodeMapForTesting.getCountryCodeToRegionCodeMap()); } - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testGetInstanceLoadUSMetadata() { PhoneMetadata metadata = phoneUtil.getMetadataForRegion("US"); assertEquals("US", metadata.getId()); @@ -92,12 +128,12 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals(49, metadata.getCountryCode()); assertEquals("00", metadata.getInternationalPrefix()); assertEquals("0", metadata.getNationalPrefix()); - assertEquals(5, metadata.getNumberFormatCount()); - assertEquals(1, metadata.getNumberFormat(4).getLeadingDigitsPatternCount()); - assertEquals("900", metadata.getNumberFormat(4).getLeadingDigitsPattern(0)); + assertEquals(6, metadata.getNumberFormatCount()); + assertEquals(1, metadata.getNumberFormat(5).getLeadingDigitsPatternCount()); + assertEquals("900", metadata.getNumberFormat(5).getLeadingDigitsPattern(0)); assertEquals("(\\d{3})(\\d{3,4})(\\d{4})", - metadata.getNumberFormat(4).getPattern()); - assertEquals("$1 $2 $3", metadata.getNumberFormat(4).getFormat()); + metadata.getNumberFormat(5).getPattern()); + assertEquals("$1 $2 $3", metadata.getNumberFormat(5).getFormat()); assertEquals("(?:[24-6]\\d{2}|3[03-9]\\d|[789](?:[1-9]\\d|0[2-9]))\\d{3,8}", metadata.getFixedLine().getNationalNumberPattern()); assertEquals("\\d{2,14}", metadata.getFixedLine().getPossibleNumberPattern()); @@ -123,104 +159,78 @@ public class PhoneNumberUtilTest extends TestCase { } public void testGetLengthOfGeographicalAreaCode() { - PhoneNumber number = new PhoneNumber(); // Google MTV, which has area code "650". - number.setCountryCode(1).setNationalNumber(6502530000L); - assertEquals(3, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(3, phoneUtil.getLengthOfGeographicalAreaCode(US_NUMBER)); // A North America toll-free number, which has no area code. - number.setCountryCode(1).setNationalNumber(8002530000L); - assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(number)); - - // An invalid US number (1 digit shorter), which has no area code. - number.setCountryCode(1).setNationalNumber(650253000L); - assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(US_TOLLFREE)); // Google London, which has area code "20". - number.setCountryCode(44).setNationalNumber(2070313000L); - assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(GB_NUMBER)); // A UK mobile phone, which has no area code. - number.setCountryCode(44).setNationalNumber(7123456789L); - assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(GB_MOBILE)); // Google Buenos Aires, which has area code "11". - number.setCountryCode(54).setNationalNumber(1155303000L); - assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(AR_NUMBER)); // Google Sydney, which has area code "2". - number.setCountryCode(61).setNationalNumber(293744000L); - assertEquals(1, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(1, phoneUtil.getLengthOfGeographicalAreaCode(AU_NUMBER)); // Google Singapore. Singapore has no area code and no national prefix. - number.setCountryCode(65).setNationalNumber(65218000L); - assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(number)); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(SG_NUMBER)); + + // An invalid US number (1 digit shorter), which has no area code. + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(US_SHORT_BY_ONE_NUMBER)); } public void testGetLengthOfNationalDestinationCode() { - PhoneNumber number = new PhoneNumber(); // Google MTV, which has national destination code (NDC) "650". - number.setCountryCode(1).setNationalNumber(6502530000L); - assertEquals(3, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(3, phoneUtil.getLengthOfNationalDestinationCode(US_NUMBER)); // A North America toll-free number, which has NDC "800". - number.setCountryCode(1).setNationalNumber(8002530000L); - assertEquals(3, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(3, phoneUtil.getLengthOfNationalDestinationCode(US_TOLLFREE)); // Google London, which has NDC "20". - number.setCountryCode(44).setNationalNumber(2070313000L); - assertEquals(2, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(2, phoneUtil.getLengthOfNationalDestinationCode(GB_NUMBER)); - // A UK mobile phone, which has NDC "7123". - number.setCountryCode(44).setNationalNumber(7123456789L); - assertEquals(4, phoneUtil.getLengthOfNationalDestinationCode(number)); + // A UK mobile phone, which has NDC "7912". + assertEquals(4, phoneUtil.getLengthOfNationalDestinationCode(GB_MOBILE)); // Google Buenos Aires, which has NDC "11". - number.setCountryCode(54).setNationalNumber(1155303000L); - assertEquals(2, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(2, phoneUtil.getLengthOfNationalDestinationCode(AR_NUMBER)); // An Argentinian mobile which has NDC "911". - number.setCountryCode(54).setNationalNumber(91155303001L); - assertEquals(3, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(3, phoneUtil.getLengthOfNationalDestinationCode(AR_MOBILE)); // Google Sydney, which has NDC "2". - number.setCountryCode(61).setNationalNumber(293744000L); - assertEquals(1, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(1, phoneUtil.getLengthOfNationalDestinationCode(AU_NUMBER)); // Google Singapore, which has NDC "6521". - number.setCountryCode(65).setNationalNumber(65218000L); - assertEquals(4, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(4, phoneUtil.getLengthOfNationalDestinationCode(SG_NUMBER)); // An invalid US number (1 digit shorter), which has no NDC. - number.setCountryCode(1).setNationalNumber(650253000L); - assertEquals(0, phoneUtil.getLengthOfNationalDestinationCode(number)); + assertEquals(0, phoneUtil.getLengthOfNationalDestinationCode(US_SHORT_BY_ONE_NUMBER)); // A number containing an invalid country code, which shouldn't have any NDC. - number.setCountryCode(123).setNationalNumber(6502530000L); + PhoneNumber number = new PhoneNumber().setCountryCode(123).setNationalNumber(6502530000L); assertEquals(0, phoneUtil.getLengthOfNationalDestinationCode(number)); } public void testGetNationalSignificantNumber() { - PhoneNumber number = new PhoneNumber(); - number.setCountryCode(1).setNationalNumber(6502530000L); - assertEquals("6502530000", PhoneNumberUtil.getNationalSignificantNumber(number)); + assertEquals("6502530000", PhoneNumberUtil.getNationalSignificantNumber(US_NUMBER)); // An Italian mobile number. - number.setCountryCode(39).setNationalNumber(312345678L); - assertEquals("312345678", PhoneNumberUtil.getNationalSignificantNumber(number)); + assertEquals("345678901", PhoneNumberUtil.getNationalSignificantNumber(IT_MOBILE)); // An Italian fixed line number. - number.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); - assertEquals("0236618300", PhoneNumberUtil.getNationalSignificantNumber(number)); + assertEquals("0236618300", PhoneNumberUtil.getNationalSignificantNumber(IT_NUMBER)); } public void testGetExampleNumber() { - PhoneNumber deNumber = new PhoneNumber(); - deNumber.setCountryCode(49).setNationalNumber(30123456); - assertEquals(deNumber, phoneUtil.getExampleNumber("DE")); - assertEquals(deNumber, phoneUtil.getExampleNumber("de")); + assertEquals(DE_NUMBER, phoneUtil.getExampleNumber("DE")); - assertEquals(deNumber, + assertEquals(DE_NUMBER, phoneUtil.getExampleNumberForType("DE", PhoneNumberUtil.PhoneNumberType.FIXED_LINE)); assertEquals(null, @@ -273,49 +283,27 @@ public class PhoneNumberUtilTest extends TestCase { } public void testFormatUSNumber() { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); - assertEquals("650 253 0000", phoneUtil.format(usNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+1 650 253 0000", phoneUtil.format(usNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("650 253 0000", phoneUtil.format(US_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+1 650 253 0000", phoneUtil.format(US_NUMBER, PhoneNumberFormat.INTERNATIONAL)); - usNumber.clear(); - usNumber.setCountryCode(1).setNationalNumber(8002530000L); - assertEquals("800 253 0000", phoneUtil.format(usNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+1 800 253 0000", phoneUtil.format(usNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("800 253 0000", phoneUtil.format(US_TOLLFREE, PhoneNumberFormat.NATIONAL)); + assertEquals("+1 800 253 0000", phoneUtil.format(US_TOLLFREE, PhoneNumberFormat.INTERNATIONAL)); - usNumber.clear(); - usNumber.setCountryCode(1).setNationalNumber(9002530000L); - assertEquals("900 253 0000", phoneUtil.format(usNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+1 900 253 0000", phoneUtil.format(usNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("900 253 0000", phoneUtil.format(US_PREMIUM, PhoneNumberFormat.NATIONAL)); + assertEquals("+1 900 253 0000", phoneUtil.format(US_PREMIUM, PhoneNumberFormat.INTERNATIONAL)); } public void testFormatBSNumber() { - PhoneNumber bsNumber = new PhoneNumber(); - bsNumber.setCountryCode(1).setNationalNumber(2421234567L); - assertEquals("242 123 4567", phoneUtil.format(bsNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+1 242 123 4567", phoneUtil.format(bsNumber, PhoneNumberFormat.INTERNATIONAL)); - - bsNumber.clear(); - bsNumber.setCountryCode(1).setNationalNumber(8002530000L); - assertEquals("800 253 0000", phoneUtil.format(bsNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+1 800 253 0000", phoneUtil.format(bsNumber, PhoneNumberFormat.INTERNATIONAL)); - - bsNumber.clear(); - bsNumber.setCountryCode(1).setNationalNumber(9002530000L); - assertEquals("900 253 0000", phoneUtil.format(bsNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+1 900 253 0000", phoneUtil.format(bsNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("242 365 1234", phoneUtil.format(BS_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+1 242 365 1234", phoneUtil.format(BS_NUMBER, PhoneNumberFormat.INTERNATIONAL)); } public void testFormatGBNumber() { - PhoneNumber gbNumber = new PhoneNumber(); - gbNumber.setCountryCode(44).setNationalNumber(2087389353L); - assertEquals("(020) 8738 9353", phoneUtil.format(gbNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+44 20 8738 9353", phoneUtil.format(gbNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("(020) 7031 3000", phoneUtil.format(GB_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+44 20 7031 3000", phoneUtil.format(GB_NUMBER, PhoneNumberFormat.INTERNATIONAL)); - gbNumber.clear(); - gbNumber.setCountryCode(44).setNationalNumber(7912345678L); - assertEquals("(07912) 345 678", phoneUtil.format(gbNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+44 7912 345 678", phoneUtil.format(gbNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("(07912) 345 678", phoneUtil.format(GB_MOBILE, PhoneNumberFormat.NATIONAL)); + assertEquals("+44 7912 345 678", phoneUtil.format(GB_MOBILE, PhoneNumberFormat.INTERNATIONAL)); } public void testFormatDENumber() { @@ -335,159 +323,160 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals("+49 291 12345678", phoneUtil.format(deNumber, PhoneNumberFormat.INTERNATIONAL)); deNumber.clear(); - deNumber.setCountryCode(49).setNationalNumber(9123123L); - assertEquals("09123 123", phoneUtil.format(deNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+49 9123 123", phoneUtil.format(deNumber, PhoneNumberFormat.INTERNATIONAL)); + deNumber.setCountryCode(49).setNationalNumber(912312345L); + assertEquals("09123 12345", phoneUtil.format(deNumber, PhoneNumberFormat.NATIONAL)); + assertEquals("+49 9123 12345", phoneUtil.format(deNumber, PhoneNumberFormat.INTERNATIONAL)); deNumber.clear(); deNumber.setCountryCode(49).setNationalNumber(80212345L); assertEquals("08021 2345", phoneUtil.format(deNumber, PhoneNumberFormat.NATIONAL)); assertEquals("+49 8021 2345", phoneUtil.format(deNumber, PhoneNumberFormat.INTERNATIONAL)); - deNumber.clear(); - deNumber.setCountryCode(49).setNationalNumber(1234L); // Note this number is correctly formatted without national prefix. Most of the numbers that // are treated as invalid numbers by the library are short numbers, and they are usually not // dialed with national prefix. - assertEquals("1234", phoneUtil.format(deNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+49 1234", phoneUtil.format(deNumber, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("1234", phoneUtil.format(DE_SHORT_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+49 1234", phoneUtil.format(DE_SHORT_NUMBER, PhoneNumberFormat.INTERNATIONAL)); + + deNumber.setCountryCode(49).setNationalNumber(41341234); + assertEquals("04134 1234", phoneUtil.format(deNumber, PhoneNumberFormat.NATIONAL)); } public void testFormatITNumber() { - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); - assertEquals("02 3661 8300", phoneUtil.format(itNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+39 02 3661 8300", phoneUtil.format(itNumber, PhoneNumberFormat.INTERNATIONAL)); - assertEquals("+390236618300", phoneUtil.format(itNumber, PhoneNumberFormat.E164)); + assertEquals("02 3661 8300", phoneUtil.format(IT_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+39 02 3661 8300", phoneUtil.format(IT_NUMBER, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("+390236618300", phoneUtil.format(IT_NUMBER, PhoneNumberFormat.E164)); - itNumber.clear(); - itNumber.setCountryCode(39).setNationalNumber(345678901L); - assertEquals("345 678 901", phoneUtil.format(itNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+39 345 678 901", phoneUtil.format(itNumber, PhoneNumberFormat.INTERNATIONAL)); - assertEquals("+39345678901", phoneUtil.format(itNumber, PhoneNumberFormat.E164)); + assertEquals("345 678 901", phoneUtil.format(IT_MOBILE, PhoneNumberFormat.NATIONAL)); + assertEquals("+39 345 678 901", phoneUtil.format(IT_MOBILE, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("+39345678901", phoneUtil.format(IT_MOBILE, PhoneNumberFormat.E164)); } public void testFormatAUNumber() { - PhoneNumber auNumber = new PhoneNumber(); - auNumber.setCountryCode(61).setNationalNumber(236618300L); - assertEquals("02 3661 8300", phoneUtil.format(auNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+61 2 3661 8300", phoneUtil.format(auNumber, PhoneNumberFormat.INTERNATIONAL)); - assertEquals("+61236618300", phoneUtil.format(auNumber, PhoneNumberFormat.E164)); - - auNumber.clear(); - auNumber.setCountryCode(61).setNationalNumber(1800123456L); + assertEquals("02 3661 8300", phoneUtil.format(AU_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+61 2 3661 8300", phoneUtil.format(AU_NUMBER, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("+61236618300", phoneUtil.format(AU_NUMBER, PhoneNumberFormat.E164)); + + PhoneNumber auNumber = new PhoneNumber().setCountryCode(61).setNationalNumber(1800123456L); assertEquals("1800 123 456", phoneUtil.format(auNumber, PhoneNumberFormat.NATIONAL)); assertEquals("+61 1800 123 456", phoneUtil.format(auNumber, PhoneNumberFormat.INTERNATIONAL)); assertEquals("+611800123456", phoneUtil.format(auNumber, PhoneNumberFormat.E164)); } public void testFormatARNumber() { - PhoneNumber arNumber = new PhoneNumber(); - arNumber.setCountryCode(54).setNationalNumber(1187654321L); - assertEquals("011 8765-4321", phoneUtil.format(arNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+54 11 8765-4321", phoneUtil.format(arNumber, PhoneNumberFormat.INTERNATIONAL)); - assertEquals("+541187654321", phoneUtil.format(arNumber, PhoneNumberFormat.E164)); + assertEquals("011 8765-4321", phoneUtil.format(AR_NUMBER, PhoneNumberFormat.NATIONAL)); + assertEquals("+54 11 8765-4321", phoneUtil.format(AR_NUMBER, PhoneNumberFormat.INTERNATIONAL)); + assertEquals("+541187654321", phoneUtil.format(AR_NUMBER, PhoneNumberFormat.E164)); - arNumber.clear(); - arNumber.setCountryCode(54).setNationalNumber(91187654321L); - assertEquals("011 15 8765-4321", phoneUtil.format(arNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("+54 9 11 8765 4321", phoneUtil.format(arNumber, PhoneNumberFormat.INTERNATIONAL)); - assertEquals("+5491187654321", phoneUtil.format(arNumber, PhoneNumberFormat.E164)); + assertEquals("011 15 8765-4321", phoneUtil.format(AR_MOBILE, PhoneNumberFormat.NATIONAL)); + assertEquals("+54 9 11 8765 4321", phoneUtil.format(AR_MOBILE, + PhoneNumberFormat.INTERNATIONAL)); + assertEquals("+5491187654321", phoneUtil.format(AR_MOBILE, PhoneNumberFormat.E164)); } public void testFormatOutOfCountryCallingNumber() { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(9002530000L); assertEquals("00 1 900 253 0000", - phoneUtil.formatOutOfCountryCallingNumber(usNumber, "DE")); + phoneUtil.formatOutOfCountryCallingNumber(US_PREMIUM, "DE")); - usNumber.clear(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); assertEquals("1 650 253 0000", - phoneUtil.formatOutOfCountryCallingNumber(usNumber, "BS")); + phoneUtil.formatOutOfCountryCallingNumber(US_NUMBER, "BS")); assertEquals("0~0 1 650 253 0000", - phoneUtil.formatOutOfCountryCallingNumber(usNumber, "PL")); + phoneUtil.formatOutOfCountryCallingNumber(US_NUMBER, "PL")); - PhoneNumber gbNumber = new PhoneNumber(); - gbNumber.setCountryCode(44).setNationalNumber(7912345678L); assertEquals("011 44 7912 345 678", - phoneUtil.formatOutOfCountryCallingNumber(gbNumber, "US")); + phoneUtil.formatOutOfCountryCallingNumber(GB_MOBILE, "US")); - PhoneNumber deNumber = new PhoneNumber(); - deNumber.setCountryCode(49).setNationalNumber(1234L); assertEquals("00 49 1234", - phoneUtil.formatOutOfCountryCallingNumber(deNumber, "GB")); + phoneUtil.formatOutOfCountryCallingNumber(DE_SHORT_NUMBER, "GB")); // Note this number is correctly formatted without national prefix. Most of the numbers that // are treated as invalid numbers by the library are short numbers, and they are usually not // dialed with national prefix. - assertEquals("1234", - phoneUtil.formatOutOfCountryCallingNumber(deNumber, "DE")); + assertEquals("1234", phoneUtil.formatOutOfCountryCallingNumber(DE_SHORT_NUMBER, "DE")); - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); assertEquals("011 39 02 3661 8300", - phoneUtil.formatOutOfCountryCallingNumber(itNumber, "US")); + phoneUtil.formatOutOfCountryCallingNumber(IT_NUMBER, "US")); assertEquals("02 3661 8300", - phoneUtil.formatOutOfCountryCallingNumber(itNumber, "IT")); + phoneUtil.formatOutOfCountryCallingNumber(IT_NUMBER, "IT")); assertEquals("+39 02 3661 8300", - phoneUtil.formatOutOfCountryCallingNumber(itNumber, "SG")); + phoneUtil.formatOutOfCountryCallingNumber(IT_NUMBER, "SG")); - PhoneNumber sgNumber = new PhoneNumber(); - sgNumber.setCountryCode(65).setNationalNumber(94777892L); - assertEquals("9477 7892", - phoneUtil.formatOutOfCountryCallingNumber(sgNumber, "SG")); + assertEquals("6521 8000", + phoneUtil.formatOutOfCountryCallingNumber(SG_NUMBER, "SG")); - PhoneNumber arNumber = new PhoneNumber(); - arNumber.setCountryCode(54).setNationalNumber(91187654321L); assertEquals("011 54 9 11 8765 4321", - phoneUtil.formatOutOfCountryCallingNumber(arNumber, "US")); + phoneUtil.formatOutOfCountryCallingNumber(AR_MOBILE, "US")); - arNumber.setExtension("1234"); + PhoneNumber arNumberWithExtn = new PhoneNumber().mergeFrom(AR_MOBILE).setExtension("1234"); assertEquals("011 54 9 11 8765 4321 ext. 1234", - phoneUtil.formatOutOfCountryCallingNumber(arNumber, "US")); + phoneUtil.formatOutOfCountryCallingNumber(arNumberWithExtn, "US")); assertEquals("0011 54 9 11 8765 4321 ext. 1234", - phoneUtil.formatOutOfCountryCallingNumber(arNumber, "AU")); + phoneUtil.formatOutOfCountryCallingNumber(arNumberWithExtn, "AU")); assertEquals("011 15 8765-4321 ext. 1234", - phoneUtil.formatOutOfCountryCallingNumber(arNumber, "AR")); + phoneUtil.formatOutOfCountryCallingNumber(arNumberWithExtn, "AR")); } public void testFormatOutOfCountryWithPreferredIntlPrefix() { - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); // This should use 0011, since that is the preferred international prefix (both 0011 and 0012 // are accepted as possible international prefixes in our test metadta.) assertEquals("0011 39 02 3661 8300", - phoneUtil.formatOutOfCountryCallingNumber(itNumber, "AU")); + phoneUtil.formatOutOfCountryCallingNumber(IT_NUMBER, "AU")); } public void testFormatWithCarrierCode() { + // We only support this for AR in our test metadata, and only for mobile numbers starting with + // certain values. + PhoneNumber arMobile = new PhoneNumber().setCountryCode(54).setNationalNumber(92234654321L); + assertEquals("02234 65-4321", phoneUtil.format(arMobile, PhoneNumberFormat.NATIONAL)); + // Here we force 14 as the carrier code. + assertEquals("02234 14 65-4321", + phoneUtil.formatNationalNumberWithCarrierCode(arMobile, "14")); + // Here we force the number to be shown with no carrier code. + assertEquals("02234 65-4321", + phoneUtil.formatNationalNumberWithCarrierCode(arMobile, "")); + // Here the international rule is used, so no carrier code should be present. + assertEquals("+5492234654321", phoneUtil.format(arMobile, PhoneNumberFormat.E164)); + // We don't support this for the US so there should be no change. + assertEquals("650 253 0000", phoneUtil.formatNationalNumberWithCarrierCode(US_NUMBER, "15")); + } + + public void testFormatWithPreferredCarrierCode() { // We only support this for AR in our test metadata. PhoneNumber arNumber = new PhoneNumber(); arNumber.setCountryCode(54).setNationalNumber(91234125678L); + // Test formatting with no preferred carrier code stored in the number itself. + assertEquals("01234 15 12-5678", + phoneUtil.formatNationalNumberWithPreferredCarrierCode(arNumber, "15")); + assertEquals("01234 12-5678", + phoneUtil.formatNationalNumberWithPreferredCarrierCode(arNumber, "")); + // Test formatting with preferred carrier code present. + arNumber.setPreferredDomesticCarrierCode("19"); assertEquals("01234 12-5678", phoneUtil.format(arNumber, PhoneNumberFormat.NATIONAL)); - // Test formatting with a carrier code. - assertEquals("01234 15 12-5678", phoneUtil.formatNationalNumberWithCarrierCode(arNumber, "15")); - // Here the international rule is used, so no carrier code should be present. - assertEquals("+5491234125678", phoneUtil.format(arNumber, PhoneNumberFormat.E164)); + assertEquals("01234 19 12-5678", + phoneUtil.formatNationalNumberWithPreferredCarrierCode(arNumber, "15")); + assertEquals("01234 19 12-5678", + phoneUtil.formatNationalNumberWithPreferredCarrierCode(arNumber, "")); + // When the preferred_domestic_carrier_code is present (even when it contains an empty string), + // use it instead of the default carrier code passed in. + arNumber.setPreferredDomesticCarrierCode(""); + assertEquals("01234 12-5678", + phoneUtil.formatNationalNumberWithPreferredCarrierCode(arNumber, "15")); // We don't support this for the US so there should be no change. PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(4241231234L); + usNumber.setCountryCode(1).setNationalNumber(4241231234L).setPreferredDomesticCarrierCode("99"); assertEquals("424 123 1234", phoneUtil.format(usNumber, PhoneNumberFormat.NATIONAL)); - assertEquals("424 123 1234", phoneUtil.formatNationalNumberWithCarrierCode(usNumber, "15")); + assertEquals("424 123 1234", + phoneUtil.formatNationalNumberWithPreferredCarrierCode(usNumber, "15")); } public void testFormatByPattern() { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); - NumberFormat newNumFormat = new NumberFormat(); newNumFormat.setPattern("(\\d{3})(\\d{3})(\\d{4})"); newNumFormat.setFormat("($1) $2-$3"); List newNumberFormats = new ArrayList(); newNumberFormats.add(newNumFormat); - assertEquals("(650) 253-0000", phoneUtil.formatByPattern(usNumber, PhoneNumberFormat.NATIONAL, + assertEquals("(650) 253-0000", phoneUtil.formatByPattern(US_NUMBER, PhoneNumberFormat.NATIONAL, newNumberFormats)); - assertEquals("+1 (650) 253-0000", phoneUtil.formatByPattern(usNumber, + assertEquals("+1 (650) 253-0000", phoneUtil.formatByPattern(US_NUMBER, PhoneNumberFormat.INTERNATIONAL, newNumberFormats)); @@ -495,69 +484,60 @@ public class PhoneNumberUtilTest extends TestCase { // followed. newNumFormat.setNationalPrefixFormattingRule("$NP ($FG)"); newNumFormat.setFormat("$1 $2-$3"); - PhoneNumber bsNumber = new PhoneNumber(); - bsNumber.setCountryCode(1).setNationalNumber(4168819999L); - assertEquals("1 (416) 881-9999", - phoneUtil.formatByPattern(bsNumber, PhoneNumberFormat.NATIONAL, newNumberFormats)); - assertEquals("+1 416 881-9999", - phoneUtil.formatByPattern(bsNumber, PhoneNumberFormat.INTERNATIONAL, + assertEquals("1 (242) 365-1234", + phoneUtil.formatByPattern(BS_NUMBER, PhoneNumberFormat.NATIONAL, + newNumberFormats)); + assertEquals("+1 242 365-1234", + phoneUtil.formatByPattern(BS_NUMBER, PhoneNumberFormat.INTERNATIONAL, newNumberFormats)); - - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); newNumFormat.setPattern("(\\d{2})(\\d{5})(\\d{3})"); newNumFormat.setFormat("$1-$2 $3"); newNumberFormats.set(0, newNumFormat); assertEquals("02-36618 300", - phoneUtil.formatByPattern(itNumber, PhoneNumberFormat.NATIONAL, newNumberFormats)); + phoneUtil.formatByPattern(IT_NUMBER, PhoneNumberFormat.NATIONAL, + newNumberFormats)); assertEquals("+39 02-36618 300", - phoneUtil.formatByPattern(itNumber, PhoneNumberFormat.INTERNATIONAL, + phoneUtil.formatByPattern(IT_NUMBER, PhoneNumberFormat.INTERNATIONAL, newNumberFormats)); - PhoneNumber gbNumber = new PhoneNumber(); - gbNumber.setCountryCode(44).setNationalNumber(2012345678L); - newNumFormat.setNationalPrefixFormattingRule("$NP$FG"); newNumFormat.setPattern("(\\d{2})(\\d{4})(\\d{4})"); newNumFormat.setFormat("$1 $2 $3"); newNumberFormats.set(0, newNumFormat); - assertEquals("020 1234 5678", - phoneUtil.formatByPattern(gbNumber, PhoneNumberFormat.NATIONAL, newNumberFormats)); + assertEquals("020 7031 3000", + phoneUtil.formatByPattern(GB_NUMBER, PhoneNumberFormat.NATIONAL, + newNumberFormats)); newNumFormat.setNationalPrefixFormattingRule("($NP$FG)"); - assertEquals("(020) 1234 5678", - phoneUtil.formatByPattern(gbNumber, PhoneNumberFormat.NATIONAL, newNumberFormats)); + assertEquals("(020) 7031 3000", + phoneUtil.formatByPattern(GB_NUMBER, PhoneNumberFormat.NATIONAL, + newNumberFormats)); newNumFormat.setNationalPrefixFormattingRule(""); - assertEquals("20 1234 5678", - phoneUtil.formatByPattern(gbNumber, PhoneNumberFormat.NATIONAL, newNumberFormats)); + assertEquals("20 7031 3000", + phoneUtil.formatByPattern(GB_NUMBER, PhoneNumberFormat.NATIONAL, + newNumberFormats)); - newNumFormat.setNationalPrefixFormattingRule(""); - assertEquals("+44 20 1234 5678", - phoneUtil.formatByPattern(gbNumber, PhoneNumberFormat.INTERNATIONAL, + assertEquals("+44 20 7031 3000", + phoneUtil.formatByPattern(GB_NUMBER, PhoneNumberFormat.INTERNATIONAL, newNumberFormats)); } public void testFormatE164Number() { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); - assertEquals("+16502530000", phoneUtil.format(usNumber, PhoneNumberFormat.E164)); - PhoneNumber deNumber = new PhoneNumber(); - deNumber.setCountryCode(49).setNationalNumber(301234L); - assertEquals("+49301234", phoneUtil.format(deNumber, PhoneNumberFormat.E164)); + assertEquals("+16502530000", phoneUtil.format(US_NUMBER, PhoneNumberFormat.E164)); + assertEquals("+4930123456", phoneUtil.format(DE_NUMBER, PhoneNumberFormat.E164)); } public void testFormatNumberWithExtension() { - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(33316005L).setExtension("1234"); + PhoneNumber nzNumber = new PhoneNumber().mergeFrom(NZ_NUMBER).setExtension("1234"); // Uses default extension prefix: assertEquals("03-331 6005 ext. 1234", phoneUtil.format(nzNumber, PhoneNumberFormat.NATIONAL)); // Extension prefix overridden in the territory information for the US: - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L).setExtension("4567"); - assertEquals("650 253 0000 extn. 4567", phoneUtil.format(usNumber, PhoneNumberFormat.NATIONAL)); + PhoneNumber usNumberWithExtension = new PhoneNumber().mergeFrom(US_NUMBER).setExtension("4567"); + assertEquals("650 253 0000 extn. 4567", phoneUtil.format(usNumberWithExtension, + PhoneNumberFormat.NATIONAL)); } public void testFormatUsingOriginalNumberFormat() throws Exception { @@ -578,13 +558,9 @@ public class PhoneNumberUtilTest extends TestCase { } public void testIsPremiumRate() { - PhoneNumber premiumRateNumber = new PhoneNumber(); - - premiumRateNumber.setCountryCode(1).setNationalNumber(9004433030L); - assertEquals(PhoneNumberUtil.PhoneNumberType.PREMIUM_RATE, - phoneUtil.getNumberType(premiumRateNumber)); + assertEquals(PhoneNumberUtil.PhoneNumberType.PREMIUM_RATE, phoneUtil.getNumberType(US_PREMIUM)); - premiumRateNumber.clear(); + PhoneNumber premiumRateNumber = new PhoneNumber(); premiumRateNumber.setCountryCode(39).setNationalNumber(892123L); assertEquals(PhoneNumberUtil.PhoneNumberType.PREMIUM_RATE, phoneUtil.getNumberType(premiumRateNumber)); @@ -629,67 +605,30 @@ public class PhoneNumberUtilTest extends TestCase { } public void testIsMobile() { - PhoneNumber mobileNumber = new PhoneNumber(); - - // A Bahama mobile number - mobileNumber.setCountryCode(1).setNationalNumber(2423570000L); - assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, - phoneUtil.getNumberType(mobileNumber)); - - mobileNumber.clear(); - mobileNumber.setCountryCode(39).setNationalNumber(312345678L); - assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, - phoneUtil.getNumberType(mobileNumber)); + assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, phoneUtil.getNumberType(BS_MOBILE)); + assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, phoneUtil.getNumberType(GB_MOBILE)); + assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, phoneUtil.getNumberType(IT_MOBILE)); - mobileNumber.clear(); - mobileNumber.setCountryCode(44).setNationalNumber(7912345678L); - assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, - phoneUtil.getNumberType(mobileNumber)); - - mobileNumber.clear(); + PhoneNumber mobileNumber = new PhoneNumber(); mobileNumber.setCountryCode(49).setNationalNumber(15123456789L); - assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, - phoneUtil.getNumberType(mobileNumber)); + assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, phoneUtil.getNumberType(mobileNumber)); - mobileNumber.clear(); - mobileNumber.setCountryCode(54).setNationalNumber(91187654321L); - assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, - phoneUtil.getNumberType(mobileNumber)); + assertEquals(PhoneNumberUtil.PhoneNumberType.MOBILE, phoneUtil.getNumberType(AR_MOBILE)); } public void testIsFixedLine() { - PhoneNumber fixedLineNumber = new PhoneNumber(); - - // A Bahama fixed-line number - fixedLineNumber.setCountryCode(1).setNationalNumber(2423651234L); - assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, - phoneUtil.getNumberType(fixedLineNumber)); - - // An Italian fixed-line number - fixedLineNumber.clear(); - fixedLineNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); - assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, - phoneUtil.getNumberType(fixedLineNumber)); - - fixedLineNumber.clear(); - fixedLineNumber.setCountryCode(44).setNationalNumber(2012345678L); - assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, - phoneUtil.getNumberType(fixedLineNumber)); - - fixedLineNumber.clear(); - fixedLineNumber.setCountryCode(49).setNationalNumber(301234L); - assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, - phoneUtil.getNumberType(fixedLineNumber)); + assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, phoneUtil.getNumberType(BS_NUMBER)); + assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, phoneUtil.getNumberType(IT_NUMBER)); + assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, phoneUtil.getNumberType(GB_NUMBER)); + assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE, phoneUtil.getNumberType(DE_NUMBER)); } public void testIsFixedLineAndMobile() { - PhoneNumber fixedLineAndMobileNumber = new PhoneNumber(); - fixedLineAndMobileNumber.setCountryCode(1).setNationalNumber(6502531111L); assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE_OR_MOBILE, - phoneUtil.getNumberType(fixedLineAndMobileNumber)); + phoneUtil.getNumberType(US_NUMBER)); - fixedLineAndMobileNumber.clear(); - fixedLineAndMobileNumber.setCountryCode(54).setNationalNumber(1987654321L); + PhoneNumber fixedLineAndMobileNumber = new PhoneNumber(). + setCountryCode(54).setNationalNumber(1987654321L); assertEquals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE_OR_MOBILE, phoneUtil.getNumberType(fixedLineAndMobileNumber)); } @@ -714,41 +653,29 @@ public class PhoneNumberUtilTest extends TestCase { } public void testIsUnknown() { - PhoneNumber unknownNumber = new PhoneNumber(); - unknownNumber.setCountryCode(1).setNationalNumber(65025311111L); - assertEquals(PhoneNumberUtil.PhoneNumberType.UNKNOWN, - phoneUtil.getNumberType(unknownNumber)); + // Invalid numbers should be of type UNKNOWN. + assertEquals(PhoneNumberUtil.PhoneNumberType.UNKNOWN, phoneUtil.getNumberType(US_LOCAL_NUMBER)); } public void testIsValidNumber() { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); - assertTrue(phoneUtil.isValidNumber(usNumber)); + assertTrue(phoneUtil.isValidNumber(US_NUMBER)); + assertTrue(phoneUtil.isValidNumber(IT_NUMBER)); + assertTrue(phoneUtil.isValidNumber(GB_MOBILE)); - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); - assertTrue(phoneUtil.isValidNumber(itNumber)); - - PhoneNumber gbNumber = new PhoneNumber(); - gbNumber.setCountryCode(44).setNationalNumber(7912345678L); - assertTrue(phoneUtil.isValidNumber(gbNumber)); - - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(21387835L); + PhoneNumber nzNumber = new PhoneNumber().setCountryCode(64).setNationalNumber(21387835L); assertTrue(phoneUtil.isValidNumber(nzNumber)); } public void testIsValidForRegion() { // This number is valid for the Bahamas, but is not a valid US number. - PhoneNumber bsNumber = new PhoneNumber(); - bsNumber.setCountryCode(1).setNationalNumber(2423232345L); - assertTrue(phoneUtil.isValidNumber(bsNumber)); - assertTrue(phoneUtil.isValidNumberForRegion(bsNumber, "BS")); - assertTrue(phoneUtil.isValidNumberForRegion(bsNumber, "bs")); - assertFalse(phoneUtil.isValidNumberForRegion(bsNumber, "US")); - bsNumber.setNationalNumber(2421232345L); + assertTrue(phoneUtil.isValidNumber(BS_NUMBER)); + assertTrue(phoneUtil.isValidNumberForRegion(BS_NUMBER, "BS")); + assertTrue(phoneUtil.isValidNumberForRegion(BS_NUMBER, "bs")); + assertFalse(phoneUtil.isValidNumberForRegion(BS_NUMBER, "US")); + PhoneNumber bsInvalidNumber = + new PhoneNumber().setCountryCode(1).setNationalNumber(2421232345L); // This number is no longer valid. - assertFalse(phoneUtil.isValidNumber(bsNumber)); + assertFalse(phoneUtil.isValidNumber(bsInvalidNumber)); // La Mayotte and Reunion use 'leadingDigits' to differentiate them. PhoneNumber reNumber = new PhoneNumber(); @@ -774,25 +701,23 @@ public class PhoneNumberUtilTest extends TestCase { } public void testIsNotValidNumber() { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(2530000L); - assertFalse(phoneUtil.isValidNumber(usNumber)); + assertFalse(phoneUtil.isValidNumber(US_LOCAL_NUMBER)); - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(23661830000L).setItalianLeadingZero(true); - assertFalse(phoneUtil.isValidNumber(itNumber)); + PhoneNumber invalidNumber = new PhoneNumber(); + invalidNumber.setCountryCode(39).setNationalNumber(23661830000L).setItalianLeadingZero(true); + assertFalse(phoneUtil.isValidNumber(invalidNumber)); - PhoneNumber gbNumber = new PhoneNumber(); - gbNumber.setCountryCode(44).setNationalNumber(791234567L); - assertFalse(phoneUtil.isValidNumber(gbNumber)); + invalidNumber.clear(); + invalidNumber.setCountryCode(44).setNationalNumber(791234567L); + assertFalse(phoneUtil.isValidNumber(invalidNumber)); - PhoneNumber deNumber = new PhoneNumber(); - deNumber.setCountryCode(49).setNationalNumber(1234L); - assertFalse(phoneUtil.isValidNumber(deNumber)); + invalidNumber.clear(); + invalidNumber.setCountryCode(49).setNationalNumber(1234L); + assertFalse(phoneUtil.isValidNumber(invalidNumber)); - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(3316005L); - assertFalse(phoneUtil.isValidNumber(nzNumber)); + invalidNumber.clear(); + invalidNumber.setCountryCode(64).setNationalNumber(3316005L); + assertFalse(phoneUtil.isValidNumber(invalidNumber)); } public void testGetRegionCodeForCountryCode() { @@ -802,17 +727,9 @@ public class PhoneNumberUtilTest extends TestCase { } public void testGetRegionCodeForNumber() { - PhoneNumber bsNumber = new PhoneNumber(); - bsNumber.setCountryCode(1).setNationalNumber(2423027000L); - assertEquals("BS", phoneUtil.getRegionCodeForNumber(bsNumber)); - - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); - assertEquals("US", phoneUtil.getRegionCodeForNumber(usNumber)); - - PhoneNumber gbNumber = new PhoneNumber(); - gbNumber.setCountryCode(44).setNationalNumber(7912345678L); - assertEquals("GB", phoneUtil.getRegionCodeForNumber(gbNumber)); + assertEquals("BS", phoneUtil.getRegionCodeForNumber(BS_NUMBER)); + assertEquals("US", phoneUtil.getRegionCodeForNumber(US_NUMBER)); + assertEquals("GB", phoneUtil.getRegionCodeForNumber(GB_MOBILE)); } public void testGetCountryCodeForRegion() { @@ -848,17 +765,9 @@ public class PhoneNumberUtilTest extends TestCase { } public void testIsPossibleNumber() { - PhoneNumber number = new PhoneNumber(); - number.setCountryCode(1).setNationalNumber(6502530000L); - assertTrue(phoneUtil.isPossibleNumber(number)); - - number.clear(); - number.setCountryCode(1).setNationalNumber(2530000L); - assertTrue(phoneUtil.isPossibleNumber(number)); - - number.clear(); - number.setCountryCode(44).setNationalNumber(2070313000L); - assertTrue(phoneUtil.isPossibleNumber(number)); + assertTrue(phoneUtil.isPossibleNumber(US_NUMBER)); + assertTrue(phoneUtil.isPossibleNumber(US_LOCAL_NUMBER)); + assertTrue(phoneUtil.isPossibleNumber(GB_NUMBER)); assertTrue(phoneUtil.isPossibleNumber("+1 650 253 0000", "US")); assertTrue(phoneUtil.isPossibleNumber("+1 650 GOO OGLE", "US")); @@ -874,17 +783,16 @@ public class PhoneNumberUtilTest extends TestCase { public void testIsPossibleNumberWithReason() { // FYI, national numbers for country code +1 that are within 7 to 10 digits are possible. - PhoneNumber number = new PhoneNumber(); - number.setCountryCode(1).setNationalNumber(6502530000L); assertEquals(PhoneNumberUtil.ValidationResult.IS_POSSIBLE, - phoneUtil.isPossibleNumberWithReason(number)); + phoneUtil.isPossibleNumberWithReason(US_NUMBER)); - number.clear(); - number.setCountryCode(1).setNationalNumber(2530000L); assertEquals(PhoneNumberUtil.ValidationResult.IS_POSSIBLE, - phoneUtil.isPossibleNumberWithReason(number)); + phoneUtil.isPossibleNumberWithReason(US_LOCAL_NUMBER)); - number.clear(); + assertEquals(PhoneNumberUtil.ValidationResult.TOO_LONG, + phoneUtil.isPossibleNumberWithReason(US_LONG_NUMBER)); + + PhoneNumber number = new PhoneNumber(); number.setCountryCode(0).setNationalNumber(2530000L); assertEquals(PhoneNumberUtil.ValidationResult.INVALID_COUNTRY_CODE, phoneUtil.isPossibleNumberWithReason(number)); @@ -894,11 +802,6 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals(PhoneNumberUtil.ValidationResult.TOO_SHORT, phoneUtil.isPossibleNumberWithReason(number)); - number.clear(); - number.setCountryCode(1).setNationalNumber(65025300000L); - assertEquals(PhoneNumberUtil.ValidationResult.TOO_LONG, - phoneUtil.isPossibleNumberWithReason(number)); - // Try with number that we don't have metadata for. PhoneNumber adNumber = new PhoneNumber(); adNumber.setCountryCode(376).setNationalNumber(12345L); @@ -913,11 +816,9 @@ public class PhoneNumberUtilTest extends TestCase { } public void testIsNotPossibleNumber() { - PhoneNumber number = new PhoneNumber(); - number.setCountryCode(1).setNationalNumber(65025300000L); - assertFalse(phoneUtil.isPossibleNumber(number)); + assertFalse(phoneUtil.isPossibleNumber(US_LONG_NUMBER)); - number.clear(); + PhoneNumber number = new PhoneNumber(); number.setCountryCode(1).setNationalNumber(253000L); assertFalse(phoneUtil.isPossibleNumber(number)); @@ -935,17 +836,13 @@ public class PhoneNumberUtilTest extends TestCase { public void testTruncateTooLongNumber() { // US number 650-253-0000, but entered with one additional digit at the end. - PhoneNumber tooLongNumber = new PhoneNumber(); - tooLongNumber.setCountryCode(1).setNationalNumber(65025300001L); - PhoneNumber validNumber = new PhoneNumber(); - validNumber.setCountryCode(1).setNationalNumber(6502530000L); - assertTrue(phoneUtil.truncateTooLongNumber(tooLongNumber)); - assertEquals(validNumber, tooLongNumber); + assertTrue(phoneUtil.truncateTooLongNumber(US_LONG_NUMBER)); + assertEquals(US_NUMBER, US_LONG_NUMBER); // GB number 080 1234 5678, but entered with 4 extra digits at the end. - tooLongNumber.clear(); + PhoneNumber tooLongNumber = new PhoneNumber(); tooLongNumber.setCountryCode(44).setNationalNumber(80123456780123L); - validNumber.clear(); + PhoneNumber validNumber = new PhoneNumber(); validNumber.setCountryCode(44).setNationalNumber(8012345678L); assertTrue(phoneUtil.truncateTooLongNumber(tooLongNumber)); assertEquals(validNumber, tooLongNumber); @@ -959,8 +856,7 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals(validNumber, tooLongNumber); // Tests what happens when a valid number is passed in. - PhoneNumber validNumberCopy = new PhoneNumber(); - validNumberCopy.mergeFrom(validNumber); + PhoneNumber validNumberCopy = new PhoneNumber().mergeFrom(validNumber); assertTrue(phoneUtil.truncateTooLongNumber(validNumber)); // Tests the number is not modified. assertEquals(validNumberCopy, validNumber); @@ -969,17 +865,14 @@ public class PhoneNumberUtilTest extends TestCase { PhoneNumber numberWithInvalidPrefix = new PhoneNumber(); // The test metadata says US numbers cannot have prefix 240. numberWithInvalidPrefix.setCountryCode(1).setNationalNumber(2401234567L); - PhoneNumber invalidNumberCopy = new PhoneNumber(); - invalidNumberCopy.mergeFrom(numberWithInvalidPrefix); + PhoneNumber invalidNumberCopy = new PhoneNumber().mergeFrom(numberWithInvalidPrefix); assertFalse(phoneUtil.truncateTooLongNumber(numberWithInvalidPrefix)); // Tests the number is not modified. assertEquals(invalidNumberCopy, numberWithInvalidPrefix); // Tests what happens when a too short number is passed in. - PhoneNumber tooShortNumber = new PhoneNumber(); - tooShortNumber.setCountryCode(1).setNationalNumber(1234L); - PhoneNumber tooShortNumberCopy = new PhoneNumber(); - tooShortNumberCopy.mergeFrom(tooShortNumber); + PhoneNumber tooShortNumber = new PhoneNumber().setCountryCode(1).setNationalNumber(1234L); + PhoneNumber tooShortNumberCopy = new PhoneNumber().mergeFrom(tooShortNumber); assertFalse(phoneUtil.truncateTooLongNumber(tooShortNumber)); // Tests the number is not modified. assertEquals(tooShortNumberCopy, tooShortNumber); @@ -1030,32 +923,48 @@ public class PhoneNumberUtilTest extends TestCase { } public void testMaybeStripNationalPrefix() { - String nationalPrefix = "34"; + PhoneMetadata metadata = new PhoneMetadata(); + metadata.setNationalPrefixForParsing("34"); + metadata.setGeneralDesc(new PhoneNumberDesc().setNationalNumberPattern("\\d{4,8}")); StringBuffer numberToStrip = new StringBuffer("34356778"); String strippedNumber = "356778"; - String nationalRuleRegExp = "\\d{4,7}"; - Pattern nationalRule = Pattern.compile(nationalRuleRegExp); - phoneUtil.maybeStripNationalPrefix(numberToStrip, nationalPrefix, "", nationalRule); + phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata); assertEquals("Should have had national prefix stripped.", strippedNumber, numberToStrip.toString()); // Retry stripping - now the number should not start with the national prefix, so no more // stripping should occur. - phoneUtil.maybeStripNationalPrefix(numberToStrip, nationalPrefix, "", nationalRule); + phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata); assertEquals("Should have had no change - no national prefix present.", strippedNumber, numberToStrip.toString()); // Some countries have no national prefix. Repeat test with none specified. - nationalPrefix = ""; - phoneUtil.maybeStripNationalPrefix(numberToStrip, nationalPrefix, "", nationalRule); + metadata.setNationalPrefixForParsing(""); + phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata); assertEquals("Should not strip anything with empty national prefix.", strippedNumber, numberToStrip.toString()); // If the resultant number doesn't match the national rule, it shouldn't be stripped. - nationalPrefix = "3"; + metadata.setNationalPrefixForParsing("3"); numberToStrip = new StringBuffer("3123"); strippedNumber = "3123"; - phoneUtil.maybeStripNationalPrefix(numberToStrip, nationalPrefix, "", nationalRule); + phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata); assertEquals("Should have had no change - after stripping, it wouldn't have matched " + "the national rule.", strippedNumber, numberToStrip.toString()); + // Test extracting carrier selection code. + metadata.setNationalPrefixForParsing("0(81)?"); + numberToStrip = new StringBuffer("08122123456"); + strippedNumber = "22123456"; + assertEquals("81", phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata)); + assertEquals("Should have had national prefix and carrier code stripped.", + strippedNumber, numberToStrip.toString()); + // If there was a transform rule, check it was applied. + metadata.setNationalPrefixTransformRule("5$15"); + // Note that a capturing group is present here. + metadata.setNationalPrefixForParsing("0(\\d{2})"); + numberToStrip = new StringBuffer("031123"); + String transformedNumber = "5315123"; + phoneUtil.maybeStripNationalPrefixAndCarrierCode(numberToStrip, metadata); + assertEquals("Should transform the 031 to a 5315.", + transformedNumber, numberToStrip.toString()); } public void testMaybeStripInternationalPrefix() { @@ -1236,32 +1145,27 @@ public class PhoneNumberUtilTest extends TestCase { } public void testParseNationalNumber() throws Exception { - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(33316005L); - // National prefix attached. - assertEquals(nzNumber, phoneUtil.parse("033316005", "NZ")); - assertEquals(nzNumber, phoneUtil.parse("033316005", "nz")); - assertEquals(nzNumber, phoneUtil.parse("33316005", "NZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("033316005", "NZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("033316005", "nz")); + assertEquals(NZ_NUMBER, phoneUtil.parse("33316005", "NZ")); // National prefix attached and some formatting present. - assertEquals(nzNumber, phoneUtil.parse("03-331 6005", "NZ")); - assertEquals(nzNumber, phoneUtil.parse("03 331 6005", "NZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("03-331 6005", "NZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("03 331 6005", "NZ")); // Testing international prefixes. // Should strip country code. - assertEquals(nzNumber, phoneUtil.parse("0064 3 331 6005", "NZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("0064 3 331 6005", "NZ")); // Try again, but this time we have an international number with Region Code US. It should // recognise the country code and parse accordingly. - assertEquals(nzNumber, phoneUtil.parse("01164 3 331 6005", "US")); - assertEquals(nzNumber, phoneUtil.parse("+64 3 331 6005", "US")); + assertEquals(NZ_NUMBER, phoneUtil.parse("01164 3 331 6005", "US")); + assertEquals(NZ_NUMBER, phoneUtil.parse("+64 3 331 6005", "US")); - nzNumber.clear(); + PhoneNumber nzNumber = new PhoneNumber(); nzNumber.setCountryCode(64).setNationalNumber(64123456L); assertEquals(nzNumber, phoneUtil.parse("64(0)64123456", "NZ")); // Check that using a "/" is fine in a phone number. - PhoneNumber deNumber = new PhoneNumber(); - deNumber.setCountryCode(49).setNationalNumber(12345678L); - assertEquals(deNumber, phoneUtil.parse("123/45678", "DE")); + assertEquals(DE_NUMBER, phoneUtil.parse("301/23456", "DE")); PhoneNumber usNumber = new PhoneNumber(); // Check it doesn't use the '1' as a country code when parsing if the phone number was already @@ -1286,48 +1190,43 @@ public class PhoneNumberUtilTest extends TestCase { } public void testParseWithInternationalPrefixes() throws Exception { - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6503336000L); - assertEquals(usNumber, phoneUtil.parse("+1 (650) 333-6000", "NZ")); - assertEquals(usNumber, phoneUtil.parse("1-650-333-6000", "US")); + assertEquals(US_NUMBER, phoneUtil.parse("+1 (650) 253-0000", "NZ")); + assertEquals(US_NUMBER, phoneUtil.parse("1-650-253-0000", "US")); // Calling the US number from Singapore by using different service providers // 1st test: calling using SingTel IDD service (IDD is 001) - assertEquals(usNumber, phoneUtil.parse("0011-650-333-6000", "SG")); + assertEquals(US_NUMBER, phoneUtil.parse("0011-650-253-0000", "SG")); // 2nd test: calling using StarHub IDD service (IDD is 008) - assertEquals(usNumber, phoneUtil.parse("0081-650-333-6000", "SG")); + assertEquals(US_NUMBER, phoneUtil.parse("0081-650-253-0000", "SG")); // 3rd test: calling using SingTel V019 service (IDD is 019) - assertEquals(usNumber, phoneUtil.parse("0191-650-333-6000", "SG")); + assertEquals(US_NUMBER, phoneUtil.parse("0191-650-253-0000", "SG")); // Calling the US number from Poland - assertEquals(usNumber, phoneUtil.parse("0~01-650-333-6000", "PL")); + assertEquals(US_NUMBER, phoneUtil.parse("0~01-650-253-0000", "PL")); // Using "++" at the start. - assertEquals(usNumber, phoneUtil.parse("++1 (650) 333-6000", "PL")); + assertEquals(US_NUMBER, phoneUtil.parse("++1 (650) 253-0000", "PL")); // Using a full-width plus sign. - assertEquals(usNumber, phoneUtil.parse("\uFF0B1 (650) 333-6000", "SG")); + assertEquals(US_NUMBER, phoneUtil.parse("\uFF0B1 (650) 253-0000", "SG")); // The whole number, including punctuation, is here represented in full-width form. - assertEquals(usNumber, phoneUtil.parse("\uFF0B\uFF11\u3000\uFF08\uFF16\uFF15\uFF10\uFF09" + - "\u3000\uFF13\uFF13\uFF13\uFF0D\uFF16\uFF10\uFF10\uFF10", - "SG")); + assertEquals(US_NUMBER, phoneUtil.parse("\uFF0B\uFF11\u3000\uFF08\uFF16\uFF15\uFF10\uFF09" + + "\u3000\uFF12\uFF15\uFF13\uFF0D\uFF10\uFF10\uFF10" + + "\uFF10", + "SG")); // Using U+30FC dash instead. - assertEquals(usNumber, phoneUtil.parse("\uFF0B\uFF11\u3000\uFF08\uFF16\uFF15\uFF10\uFF09" + - "\u3000\uFF13\uFF13\uFF13\u30FC\uFF16\uFF10\uFF10\uFF10", - "SG")); + assertEquals(US_NUMBER, phoneUtil.parse("\uFF0B\uFF11\u3000\uFF08\uFF16\uFF15\uFF10\uFF09" + + "\u3000\uFF12\uFF15\uFF13\u30FC\uFF10\uFF10\uFF10" + + "\uFF10", + "SG")); } public void testParseWithLeadingZero() throws Exception { - PhoneNumber itNumber = new PhoneNumber(); - itNumber.setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true); - assertEquals(itNumber, phoneUtil.parse("+39 02-36618 300", "NZ")); - assertEquals(itNumber, phoneUtil.parse("02-36618 300", "IT")); + assertEquals(IT_NUMBER, phoneUtil.parse("+39 02-36618 300", "NZ")); + assertEquals(IT_NUMBER, phoneUtil.parse("02-36618 300", "IT")); - itNumber.clear(); - itNumber.setCountryCode(39).setNationalNumber(312345678L); - assertEquals(itNumber, phoneUtil.parse("312 345 678", "IT")); + assertEquals(IT_MOBILE, phoneUtil.parse("345 678 901", "IT")); } public void testParseNationalNumberArgentina() throws Exception { // Test parsing mobile numbers of Argentina. PhoneNumber arNumber = new PhoneNumber(); - arNumber.setCountryCode(54).setNationalNumber(93435551212L); assertEquals(arNumber, phoneUtil.parse("+54 9 343 555 1212", "AR")); assertEquals(arNumber, phoneUtil.parse("0343 15 555 1212", "AR")); @@ -1336,12 +1235,11 @@ public class PhoneNumberUtilTest extends TestCase { arNumber.setCountryCode(54).setNationalNumber(93715654320L); assertEquals(arNumber, phoneUtil.parse("+54 9 3715 65 4320", "AR")); assertEquals(arNumber, phoneUtil.parse("03715 15 65 4320", "AR")); + assertEquals(AR_MOBILE, phoneUtil.parse("911 876 54321", "AR")); // Test parsing fixed-line numbers of Argentina. - arNumber.clear(); - arNumber.setCountryCode(54).setNationalNumber(1137970000L); - assertEquals(arNumber, phoneUtil.parse("+54 11 3797 0000", "AR")); - assertEquals(arNumber, phoneUtil.parse("011 3797 0000", "AR")); + assertEquals(AR_NUMBER, phoneUtil.parse("+54 11 8765 4321", "AR")); + assertEquals(AR_NUMBER, phoneUtil.parse("011 8765 4321", "AR")); arNumber.clear(); arNumber.setCountryCode(54).setNationalNumber(3715654321L); @@ -1356,12 +1254,10 @@ public class PhoneNumberUtilTest extends TestCase { public void testParseWithXInNumber() throws Exception { // Test that having an 'x' in the phone number at the start is ok and that it just gets removed. - PhoneNumber arNumber = new PhoneNumber(); - arNumber.setCountryCode(54).setNationalNumber(123456789L); - assertEquals(arNumber, phoneUtil.parse("0123456789", "AR")); - assertEquals(arNumber, phoneUtil.parse("(0) 123456789", "AR")); - assertEquals(arNumber, phoneUtil.parse("0 123456789", "AR")); - assertEquals(arNumber, phoneUtil.parse("(0xx) 123456789", "AR")); + assertEquals(AR_NUMBER, phoneUtil.parse("01187654321", "AR")); + assertEquals(AR_NUMBER, phoneUtil.parse("(0) 1187654321", "AR")); + assertEquals(AR_NUMBER, phoneUtil.parse("0 1187654321", "AR")); + assertEquals(AR_NUMBER, phoneUtil.parse("(0xx) 1187654321", "AR")); PhoneNumber arFromUs = new PhoneNumber(); arFromUs.setCountryCode(54).setNationalNumber(81429712L); // This test is intentionally constructed such that the number of digit after xx is larger than @@ -1547,21 +1443,25 @@ public class PhoneNumberUtilTest extends TestCase { } public void testParseNumbersWithPlusWithNoRegion() throws Exception { - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(33316005L); // "ZZ" is allowed only if the number starts with a '+' - then the country code can be // calculated. - assertEquals(nzNumber, phoneUtil.parse("+64 3 331 6005", "ZZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("+64 3 331 6005", "ZZ")); // Test with full-width plus. - assertEquals(nzNumber, phoneUtil.parse("\uFF0B64 3 331 6005", "ZZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("\uFF0B64 3 331 6005", "ZZ")); // Test with normal plus but leading characters that need to be stripped. - assertEquals(nzNumber, phoneUtil.parse("Tel: +64 3 331 6005", "ZZ")); - assertEquals(nzNumber, phoneUtil.parse("+64 3 331 6005", null)); - nzNumber.setRawInput("+64 3 331 6005"). - setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN); - assertEquals(nzNumber, phoneUtil.parseAndKeepRawInput("+64 3 331 6005", "ZZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("Tel: +64 3 331 6005", "ZZ")); + assertEquals(NZ_NUMBER, phoneUtil.parse("+64 3 331 6005", null)); + + // It is important that we set the carrier code to an empty string, since we used + // ParseAndKeepRawInput and no carrier code was found. + PhoneNumber nzNumberWithRawInput = new PhoneNumber().mergeFrom(NZ_NUMBER). + setRawInput("+64 3 331 6005"). + setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN). + setPreferredDomesticCarrierCode(""); + assertEquals(nzNumberWithRawInput, phoneUtil.parseAndKeepRawInput("+64 3 331 6005", + "ZZ")); // Null is also allowed for the region code in these cases. - assertEquals(nzNumber, phoneUtil.parseAndKeepRawInput("+64 3 331 6005", null)); + assertEquals(nzNumberWithRawInput, phoneUtil.parseAndKeepRawInput("+64 3 331 6005", null)); } public void testParseExtensions() throws Exception { @@ -1572,15 +1472,12 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals(nzNumber, phoneUtil.parse("03-3316005 int.3456", "NZ")); assertEquals(nzNumber, phoneUtil.parse("03 3316005 #3456", "NZ")); // Test the following do not extract extensions: - PhoneNumber nonExtnNumber = new PhoneNumber(); - nonExtnNumber.setCountryCode(1).setNationalNumber(80074935247L); - assertEquals(nonExtnNumber, phoneUtil.parse("1800 six-flags", "US")); - assertEquals(nonExtnNumber, phoneUtil.parse("1800 SIX FLAGS", "US")); - assertEquals(nonExtnNumber, phoneUtil.parse("0~0 1800 7493 5247", "PL")); - assertEquals(nonExtnNumber, phoneUtil.parse("(1800) 7493.5247", "US")); + assertEquals(ALPHA_NUMERIC_NUMBER, phoneUtil.parse("1800 six-flags", "US")); + assertEquals(ALPHA_NUMERIC_NUMBER, phoneUtil.parse("1800 SIX FLAGS", "US")); + assertEquals(ALPHA_NUMERIC_NUMBER, phoneUtil.parse("0~0 1800 7493 5247", "PL")); + assertEquals(ALPHA_NUMERIC_NUMBER, phoneUtil.parse("(1800) 7493.5247", "US")); // Check that the last instance of an extension token is matched. - PhoneNumber extnNumber = new PhoneNumber(); - extnNumber.setCountryCode(1).setNationalNumber(80074935247L).setExtension("1234"); + PhoneNumber extnNumber = new PhoneNumber().mergeFrom(ALPHA_NUMERIC_NUMBER).setExtension("1234"); assertEquals(extnNumber, phoneUtil.parse("0~0 1800 7493 5247 ~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. @@ -1629,29 +1526,29 @@ public class PhoneNumberUtilTest extends TestCase { } public void testParseAndKeepRaw() throws Exception { - PhoneNumber alphaNumericNumber = new PhoneNumber(); - alphaNumericNumber. - setCountryCode(1).setNationalNumber(80074935247L).setRawInput("800 six-flags"). - setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY); + PhoneNumber alphaNumericNumber = new PhoneNumber().mergeFrom(ALPHA_NUMERIC_NUMBER). + setRawInput("800 six-flags"). + setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY). + setPreferredDomesticCarrierCode(""); assertEquals(alphaNumericNumber, phoneUtil.parseAndKeepRawInput("800 six-flags", "US")); - alphaNumericNumber. - setCountryCode(1).setNationalNumber(8007493524L).setRawInput("1800 six-flag"). - setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN); - assertEquals(alphaNumericNumber, + PhoneNumber shorterAlphaNumber = new PhoneNumber(). + setCountryCode(1).setNationalNumber(8007493524L). + setRawInput("1800 six-flag"). + setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN). + setPreferredDomesticCarrierCode(""); + assertEquals(shorterAlphaNumber, phoneUtil.parseAndKeepRawInput("1800 six-flag", "US")); - alphaNumericNumber. - setCountryCode(1).setNationalNumber(8007493524L).setRawInput("+1800 six-flag"). + shorterAlphaNumber.setRawInput("+1800 six-flag"). setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN); - assertEquals(alphaNumericNumber, + assertEquals(shorterAlphaNumber, phoneUtil.parseAndKeepRawInput("+1800 six-flag", "NZ")); - alphaNumericNumber. - setCountryCode(1).setNationalNumber(8007493524L).setRawInput("001800 six-flag"). + shorterAlphaNumber.setRawInput("001800 six-flag"). setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_IDD); - assertEquals(alphaNumericNumber, + assertEquals(shorterAlphaNumber, phoneUtil.parseAndKeepRawInput("001800 six-flag", "NZ")); // Invalid region code supplied. @@ -1664,6 +1561,12 @@ public class PhoneNumberUtilTest extends TestCase { NumberParseException.ErrorType.INVALID_COUNTRY_CODE, e.getErrorType()); } + + PhoneNumber koreanNumber = new PhoneNumber(); + koreanNumber.setCountryCode(82).setNationalNumber(22123456).setRawInput("08122123456"). + setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY). + setPreferredDomesticCarrierCode("81"); + assertEquals(koreanNumber, phoneUtil.parseAndKeepRawInput("08122123456", "KR")); } public void testCountryWithNoNumberDesc() { @@ -1677,10 +1580,8 @@ public class PhoneNumberUtilTest extends TestCase { assertTrue(phoneUtil.isValidNumber(adNumber)); // Test dialing a US number from within Andorra. - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(6502530000L); assertEquals("00 1 650 253 0000", - phoneUtil.formatOutOfCountryCallingNumber(usNumber, "AD")); + phoneUtil.formatOutOfCountryCallingNumber(US_NUMBER, "AD")); } public void testUnknownCountryCallingCodeForValidation() { @@ -1709,23 +1610,32 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH, phoneUtil.isNumberMatch("+64 3 331-6005 extn 1234", "+6433316005#1234")); // Test proto buffers. - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(33316005L).setExtension("3456"); assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH, - phoneUtil.isNumberMatch(nzNumber, "+643 331 6005 ext 3456")); - nzNumber.clearExtension(); + phoneUtil.isNumberMatch(NZ_NUMBER, "+6403 331 6005")); + + PhoneNumber nzNumber = new PhoneNumber().mergeFrom(NZ_NUMBER).setExtension("3456"); assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH, - phoneUtil.isNumberMatch(nzNumber, "+6403 331 6005")); + phoneUtil.isNumberMatch(nzNumber, "+643 331 6005 ext 3456")); // Check empty extensions are ignored. nzNumber.setExtension(""); assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH, phoneUtil.isNumberMatch(nzNumber, "+6403 331 6005")); // Check variant with two proto buffers. - PhoneNumber nzNumberTwo = new PhoneNumber(); - nzNumberTwo.setCountryCode(64).setNationalNumber(33316005L); - assertEquals("Number " + nzNumber.toString() + " did not match " + nzNumberTwo.toString(), + assertEquals("Number " + nzNumber.toString() + " did not match " + NZ_NUMBER.toString(), PhoneNumberUtil.MatchType.EXACT_MATCH, - phoneUtil.isNumberMatch(nzNumber, nzNumberTwo)); + phoneUtil.isNumberMatch(nzNumber, NZ_NUMBER)); + + // Check raw_input, country_code_source and preferred_domestic_carrier_code are ignored. + PhoneNumber brNumberOne = new PhoneNumber(); + PhoneNumber brNumberTwo = new PhoneNumber(); + brNumberOne.setCountryCode(55).setNationalNumber(3121286979L) + .setCountryCodeSource(PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN) + .setPreferredDomesticCarrierCode("12").setRawInput("012 3121286979"); + brNumberTwo.setCountryCode(55).setNationalNumber(3121286979L) + .setCountryCodeSource(PhoneNumber.CountryCodeSource.FROM_DEFAULT_COUNTRY) + .setPreferredDomesticCarrierCode("14").setRawInput("143121286979"); + assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH, + phoneUtil.isNumberMatch(brNumberOne, brNumberTwo)); } public void testIsNumberMatchNonMatches() throws Exception { @@ -1748,7 +1658,6 @@ public class PhoneNumberUtilTest extends TestCase { // Invalid numbers that can't be parsed. assertEquals(PhoneNumberUtil.MatchType.NOT_A_NUMBER, phoneUtil.isNumberMatch("43", "3 331 6043")); - // Invalid numbers that can't be parsed. assertEquals(PhoneNumberUtil.MatchType.NOT_A_NUMBER, phoneUtil.isNumberMatch("+43", "+64 3 331 6005")); assertEquals(PhoneNumberUtil.MatchType.NOT_A_NUMBER, @@ -1763,39 +1672,34 @@ public class PhoneNumberUtilTest extends TestCase { phoneUtil.isNumberMatch("+64 3 331-6005", "03 331 6005")); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, phoneUtil.isNumberMatch("3 331-6005", "03 331 6005")); - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(33316005L).setExtension(""); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch(nzNumber, "03 331 6005")); + phoneUtil.isNumberMatch(NZ_NUMBER, "03 331 6005")); // Here the second number possibly starts with the country code for New Zealand, although we are // unsure. + PhoneNumber unchangedNzNumber = new PhoneNumber().mergeFrom(NZ_NUMBER); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch(nzNumber, "(64-3) 331 6005")); - PhoneNumber unchangedNzNumber = new PhoneNumber(); - unchangedNzNumber.setCountryCode(64).setNationalNumber(33316005L).setExtension(""); + phoneUtil.isNumberMatch(unchangedNzNumber, "(64-3) 331 6005")); // Check the phone number proto was not edited during the method call. - assertEquals(unchangedNzNumber, nzNumber); + assertEquals(NZ_NUMBER, unchangedNzNumber); // Here, the 1 might be a national prefix, if we compare it to the US number, so the resultant // match is an NSN match. - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(2345678901L).setExtension(""); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch(usNumber, "1-234-567-8901")); + phoneUtil.isNumberMatch(US_NUMBER, "1-650-253-0000")); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch(usNumber, "2345678901")); + phoneUtil.isNumberMatch(US_NUMBER, "6502530000")); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch("+1 234-567 8901", "1 234 567 8901")); + phoneUtil.isNumberMatch("+1 650-253 0000", "1 650 253 0000")); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch("1 234-567 8901", "1 234 567 8901")); + phoneUtil.isNumberMatch("1 650-253 0000", "1 650 253 0000")); assertEquals(PhoneNumberUtil.MatchType.NSN_MATCH, - phoneUtil.isNumberMatch("1 234-567 8901", "+1 234 567 8901")); + phoneUtil.isNumberMatch("1 650-253 0000", "+1 650 253 0000")); // For this case, the match will be a short NSN match, because we cannot assume that the 1 might // be a national prefix, so don't remove it when parsing. PhoneNumber randomNumber = new PhoneNumber(); - randomNumber.setCountryCode(41).setNationalNumber(2345678901L).setExtension(""); + randomNumber.setCountryCode(41).setNationalNumber(6502530000L); assertEquals(PhoneNumberUtil.MatchType.SHORT_NSN_MATCH, - phoneUtil.isNumberMatch(randomNumber, "1-234-567-8901")); + phoneUtil.isNumberMatch(randomNumber, "1-650-253-0000")); } public void testIsNumberMatchShortNsnMatches() throws Exception { @@ -1831,23 +1735,17 @@ public class PhoneNumberUtilTest extends TestCase { } public void testCanBeInternationallyDialled() throws Exception { - // We have no-international-dialling rules for the US in our test metadata. - PhoneNumber usNumber = new PhoneNumber(); - usNumber.setCountryCode(1).setNationalNumber(8001231234L); - assertFalse(phoneUtil.canBeInternationallyDialled(usNumber)); + // We have no-international-dialling rules for the US in our test metadata that say that + // toll-free numbers cannot be dialled internationally. + assertFalse(phoneUtil.canBeInternationallyDialled(US_TOLLFREE)); - PhoneNumber usInternationalNumber = new PhoneNumber(); - usInternationalNumber.setCountryCode(1).setNationalNumber(2311231234L); - assertTrue(phoneUtil.canBeInternationallyDialled(usInternationalNumber)); + // Normal US numbers can be internationally dialled. + assertTrue(phoneUtil.canBeInternationallyDialled(US_NUMBER)); - PhoneNumber usInvalidNumber = new PhoneNumber(); // Invalid number. - usInvalidNumber.setCountryCode(1).setNationalNumber(13112312L); - assertTrue(phoneUtil.canBeInternationallyDialled(usInvalidNumber)); + assertTrue(phoneUtil.canBeInternationallyDialled(US_LOCAL_NUMBER)); // We have no data for NZ - should return true. - PhoneNumber nzNumber = new PhoneNumber(); - nzNumber.setCountryCode(64).setNationalNumber(33316005L); - assertTrue(phoneUtil.canBeInternationallyDialled(nzNumber)); + assertTrue(phoneUtil.canBeInternationallyDialled(NZ_NUMBER)); } } diff --git a/java/test/com/google/i18n/phonenumbers/PhonenumberTest.java b/java/test/com/google/i18n/phonenumbers/PhonenumberTest.java index b77b9d1f3..5f35bb090 100644 --- a/java/test/com/google/i18n/phonenumbers/PhonenumberTest.java +++ b/java/test/com/google/i18n/phonenumbers/PhonenumberTest.java @@ -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()); + } } diff --git a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_AR b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_AR index 4a9b091ee..e2efc8f10 100644 Binary files a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_AR and b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_AR differ diff --git a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE index b2f1c5aa4..82b22fb77 100644 Binary files a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE and b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_DE differ diff --git a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_KR b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_KR index aad7e4146..7c2b9421e 100644 Binary files a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_KR and b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_KR differ