1 /* 2 * Copyright (C) 2011 The Libphonenumber Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.i18n.phonenumbers; 18 19 import com.google.i18n.phonenumbers.PhoneNumberUtil.Leniency; 20 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; 21 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; 22 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.Iterator; 26 import java.util.List; 27 import java.util.NoSuchElementException; 28 29 /** 30 * Tests for {@link PhoneNumberMatcher}. This only tests basic functionality based on test metadata. 31 * 32 * @see PhoneNumberUtilTest {@link PhoneNumberUtilTest} for the origin of the test data 33 */ 34 public class PhoneNumberMatcherTest extends TestMetadataTestCase { 35 testContainsMoreThanOneSlashInNationalNumber()36 public void testContainsMoreThanOneSlashInNationalNumber() throws Exception { 37 // A date should return true. 38 PhoneNumber number = new PhoneNumber(); 39 number.setCountryCode(1); 40 number.setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY); 41 String candidate = "1/05/2013"; 42 assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 43 44 // Here, the country code source thinks it started with a country calling code, but this is not 45 // the same as the part before the slash, so it's still true. 46 number = new PhoneNumber(); 47 number.setCountryCode(274); 48 number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN); 49 candidate = "27/4/2013"; 50 assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 51 52 // Now it should be false, because the first slash is after the country calling code. 53 number = new PhoneNumber(); 54 number.setCountryCode(49); 55 number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN); 56 candidate = "49/69/2013"; 57 assertFalse(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 58 59 number = new PhoneNumber(); 60 number.setCountryCode(49); 61 number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN); 62 candidate = "+49/69/2013"; 63 assertFalse(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 64 65 candidate = "+ 49/69/2013"; 66 assertFalse(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 67 68 candidate = "+ 49/69/20/13"; 69 assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 70 71 // Here, the first group is not assumed to be the country calling code, even though it is the 72 // same as it, so this should return true. 73 number = new PhoneNumber(); 74 number.setCountryCode(49); 75 number.setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY); 76 candidate = "49/69/2013"; 77 assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); 78 } 79 80 /** See {@link PhoneNumberUtilTest#testParseNationalNumber()}. */ testFindNationalNumber()81 public void testFindNationalNumber() throws Exception { 82 // same cases as in testParseNationalNumber 83 doTestFindInContext("033316005", RegionCode.NZ); 84 // ("33316005", RegionCode.NZ) is omitted since the national prefix is obligatory for these 85 // types of numbers in New Zealand. 86 // National prefix attached and some formatting present. 87 doTestFindInContext("03-331 6005", RegionCode.NZ); 88 doTestFindInContext("03 331 6005", RegionCode.NZ); 89 // Testing international prefixes. 90 // Should strip country code. 91 doTestFindInContext("0064 3 331 6005", RegionCode.NZ); 92 // Try again, but this time we have an international number with Region Code US. It should 93 // recognize the country code and parse accordingly. 94 doTestFindInContext("01164 3 331 6005", RegionCode.US); 95 doTestFindInContext("+64 3 331 6005", RegionCode.US); 96 97 doTestFindInContext("64(0)64123456", RegionCode.NZ); 98 // Check that using a "/" is fine in a phone number. 99 // Note that real Polish numbers do *not* start with a 0. 100 doTestFindInContext("0123/456789", RegionCode.PL); 101 doTestFindInContext("123-456-7890", RegionCode.US); 102 } 103 104 /** See {@link PhoneNumberUtilTest#testParseWithInternationalPrefixes()}. */ testFindWithInternationalPrefixes()105 public void testFindWithInternationalPrefixes() throws Exception { 106 doTestFindInContext("+1 (650) 333-6000", RegionCode.NZ); 107 doTestFindInContext("1-650-333-6000", RegionCode.US); 108 // Calling the US number from Singapore by using different service providers 109 // 1st test: calling using SingTel IDD service (IDD is 001) 110 doTestFindInContext("0011-650-333-6000", RegionCode.SG); 111 // 2nd test: calling using StarHub IDD service (IDD is 008) 112 doTestFindInContext("0081-650-333-6000", RegionCode.SG); 113 // 3rd test: calling using SingTel V019 service (IDD is 019) 114 doTestFindInContext("0191-650-333-6000", RegionCode.SG); 115 // Calling the US number from Poland 116 doTestFindInContext("0~01-650-333-6000", RegionCode.PL); 117 // Using "++" at the start. 118 doTestFindInContext("++1 (650) 333-6000", RegionCode.PL); 119 // Using a full-width plus sign. 120 doTestFindInContext("\uFF0B1 (650) 333-6000", RegionCode.SG); 121 // The whole number, including punctuation, is here represented in full-width form. 122 doTestFindInContext("\uFF0B\uFF11\u3000\uFF08\uFF16\uFF15\uFF10\uFF09" 123 + "\u3000\uFF13\uFF13\uFF13\uFF0D\uFF16\uFF10\uFF10\uFF10", 124 RegionCode.SG); 125 } 126 127 /** See {@link PhoneNumberUtilTest#testParseWithLeadingZero()}. */ testFindWithLeadingZero()128 public void testFindWithLeadingZero() throws Exception { 129 doTestFindInContext("+39 02-36618 300", RegionCode.NZ); 130 doTestFindInContext("02-36618 300", RegionCode.IT); 131 doTestFindInContext("312 345 678", RegionCode.IT); 132 } 133 134 /** See {@link PhoneNumberUtilTest#testParseNationalNumberArgentina()}. */ testFindNationalNumberArgentina()135 public void testFindNationalNumberArgentina() throws Exception { 136 // Test parsing mobile numbers of Argentina. 137 doTestFindInContext("+54 9 343 555 1212", RegionCode.AR); 138 doTestFindInContext("0343 15 555 1212", RegionCode.AR); 139 140 doTestFindInContext("+54 9 3715 65 4320", RegionCode.AR); 141 doTestFindInContext("03715 15 65 4320", RegionCode.AR); 142 143 // Test parsing fixed-line numbers of Argentina. 144 doTestFindInContext("+54 11 3797 0000", RegionCode.AR); 145 doTestFindInContext("011 3797 0000", RegionCode.AR); 146 147 doTestFindInContext("+54 3715 65 4321", RegionCode.AR); 148 doTestFindInContext("03715 65 4321", RegionCode.AR); 149 150 doTestFindInContext("+54 23 1234 0000", RegionCode.AR); 151 doTestFindInContext("023 1234 0000", RegionCode.AR); 152 } 153 154 /** See {@link PhoneNumberUtilTest#testParseWithXInNumber()}. */ testFindWithXInNumber()155 public void testFindWithXInNumber() throws Exception { 156 doTestFindInContext("(0xx) 123456789", RegionCode.AR); 157 // A case where x denotes both carrier codes and extension symbol. 158 doTestFindInContext("(0xx) 123456789 x 1234", RegionCode.AR); 159 160 // This test is intentionally constructed such that the number of digit after xx is larger than 161 // 7, so that the number won't be mistakenly treated as an extension, as we allow extensions up 162 // to 7 digits. This assumption is okay for now as all the countries where a carrier selection 163 // code is written in the form of xx have a national significant number of length larger than 7. 164 doTestFindInContext("011xx5481429712", RegionCode.US); 165 } 166 167 /** See {@link PhoneNumberUtilTest#testParseNumbersMexico()}. */ testFindNumbersMexico()168 public void testFindNumbersMexico() throws Exception { 169 // Test parsing fixed-line numbers of Mexico. 170 doTestFindInContext("+52 (449)978-0001", RegionCode.MX); 171 doTestFindInContext("01 (449)978-0001", RegionCode.MX); 172 doTestFindInContext("(449)978-0001", RegionCode.MX); 173 174 // Test parsing mobile numbers of Mexico. 175 doTestFindInContext("+52 1 33 1234-5678", RegionCode.MX); 176 doTestFindInContext("044 (33) 1234-5678", RegionCode.MX); 177 doTestFindInContext("045 33 1234-5678", RegionCode.MX); 178 } 179 180 /** See {@link PhoneNumberUtilTest#testParseNumbersWithPlusWithNoRegion()}. */ testFindNumbersWithPlusWithNoRegion()181 public void testFindNumbersWithPlusWithNoRegion() throws Exception { 182 // RegionCode.ZZ is allowed only if the number starts with a '+' - then the country code can be 183 // calculated. 184 doTestFindInContext("+64 3 331 6005", RegionCode.ZZ); 185 // Null is also allowed for the region code in these cases. 186 doTestFindInContext("+64 3 331 6005", null); 187 } 188 189 /** See {@link PhoneNumberUtilTest#testParseExtensions()}. */ testFindExtensions()190 public void testFindExtensions() throws Exception { 191 doTestFindInContext("03 331 6005 ext 3456", RegionCode.NZ); 192 doTestFindInContext("03-3316005x3456", RegionCode.NZ); 193 doTestFindInContext("03-3316005 int.3456", RegionCode.NZ); 194 doTestFindInContext("03 3316005 #3456", RegionCode.NZ); 195 doTestFindInContext("0~0 1800 7493 524", RegionCode.PL); 196 doTestFindInContext("(1800) 7493.524", RegionCode.US); 197 // Check that the last instance of an extension token is matched. 198 doTestFindInContext("0~0 1800 7493 524 ~1234", RegionCode.PL); 199 // Verifying bug-fix where the last digit of a number was previously omitted if it was a 0 when 200 // extracting the extension. Also verifying a few different cases of extensions. 201 doTestFindInContext("+44 2034567890x456", RegionCode.NZ); 202 doTestFindInContext("+44 2034567890x456", RegionCode.GB); 203 doTestFindInContext("+44 2034567890 x456", RegionCode.GB); 204 doTestFindInContext("+44 2034567890 X456", RegionCode.GB); 205 doTestFindInContext("+44 2034567890 X 456", RegionCode.GB); 206 doTestFindInContext("+44 2034567890 X 456", RegionCode.GB); 207 doTestFindInContext("+44 2034567890 X 456", RegionCode.GB); 208 209 doTestFindInContext("(800) 901-3355 x 7246433", RegionCode.US); 210 doTestFindInContext("(800) 901-3355 , ext 7246433", RegionCode.US); 211 doTestFindInContext("(800) 901-3355 ,extension 7246433", RegionCode.US); 212 // The next test differs from PhoneNumberUtil -> when matching we don't consider a lone comma to 213 // indicate an extension, although we accept it when parsing. 214 doTestFindInContext("(800) 901-3355 ,x 7246433", RegionCode.US); 215 doTestFindInContext("(800) 901-3355 ext: 7246433", RegionCode.US); 216 } 217 testFindInterspersedWithSpace()218 public void testFindInterspersedWithSpace() throws Exception { 219 doTestFindInContext("0 3 3 3 1 6 0 0 5", RegionCode.NZ); 220 } 221 222 /** 223 * Test matching behavior when starting in the middle of a phone number. 224 */ testIntermediateParsePositions()225 public void testIntermediateParsePositions() throws Exception { 226 String text = "Call 033316005 or 032316005!"; 227 // | | | | | | 228 // 0 5 10 15 20 25 229 230 // Iterate over all possible indices. 231 for (int i = 0; i <= 5; i++) { 232 assertEqualRange(text, i, 5, 14); 233 } 234 // 7 and 8 digits in a row are still parsed as number. 235 assertEqualRange(text, 6, 6, 14); 236 assertEqualRange(text, 7, 7, 14); 237 // Anything smaller is skipped to the second instance. 238 for (int i = 8; i <= 19; i++) { 239 assertEqualRange(text, i, 19, 28); 240 } 241 } 242 testFourMatchesInARow()243 public void testFourMatchesInARow() throws Exception { 244 String number1 = "415-666-7777"; 245 String number2 = "800-443-1223"; 246 String number3 = "212-443-1223"; 247 String number4 = "650-443-1223"; 248 String text = number1 + " - " + number2 + " - " + number3 + " - " + number4; 249 250 Iterator<PhoneNumberMatch> iterator = 251 phoneUtil.findNumbers(text, RegionCode.US).iterator(); 252 PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null; 253 assertMatchProperties(match, text, number1, RegionCode.US); 254 255 match = iterator.hasNext() ? iterator.next() : null; 256 assertMatchProperties(match, text, number2, RegionCode.US); 257 258 match = iterator.hasNext() ? iterator.next() : null; 259 assertMatchProperties(match, text, number3, RegionCode.US); 260 261 match = iterator.hasNext() ? iterator.next() : null; 262 assertMatchProperties(match, text, number4, RegionCode.US); 263 } 264 testMatchesFoundWithMultipleSpaces()265 public void testMatchesFoundWithMultipleSpaces() throws Exception { 266 String number1 = "(415) 666-7777"; 267 String number2 = "(800) 443-1223"; 268 String text = number1 + " " + number2; 269 270 Iterator<PhoneNumberMatch> iterator = 271 phoneUtil.findNumbers(text, RegionCode.US).iterator(); 272 PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null; 273 assertMatchProperties(match, text, number1, RegionCode.US); 274 275 match = iterator.hasNext() ? iterator.next() : null; 276 assertMatchProperties(match, text, number2, RegionCode.US); 277 } 278 testMatchWithSurroundingZipcodes()279 public void testMatchWithSurroundingZipcodes() throws Exception { 280 String number = "415-666-7777"; 281 String zipPreceding = "My address is CA 34215 - " + number + " is my number."; 282 283 Iterator<PhoneNumberMatch> iterator = 284 phoneUtil.findNumbers(zipPreceding, RegionCode.US).iterator(); 285 PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null; 286 assertMatchProperties(match, zipPreceding, number, RegionCode.US); 287 288 // Now repeat, but this time the phone number has spaces in it. It should still be found. 289 number = "(415) 666 7777"; 290 291 String zipFollowing = "My number is " + number + ". 34215 is my zip-code."; 292 iterator = phoneUtil.findNumbers(zipFollowing, RegionCode.US).iterator(); 293 PhoneNumberMatch matchWithSpaces = iterator.hasNext() ? iterator.next() : null; 294 assertMatchProperties(matchWithSpaces, zipFollowing, number, RegionCode.US); 295 } 296 testIsLatinLetter()297 public void testIsLatinLetter() throws Exception { 298 assertTrue(PhoneNumberMatcher.isLatinLetter('c')); 299 assertTrue(PhoneNumberMatcher.isLatinLetter('C')); 300 assertTrue(PhoneNumberMatcher.isLatinLetter('\u00C9')); 301 assertTrue(PhoneNumberMatcher.isLatinLetter('\u0301')); // Combining acute accent 302 // Punctuation, digits and white-space are not considered "latin letters". 303 assertFalse(PhoneNumberMatcher.isLatinLetter(':')); 304 assertFalse(PhoneNumberMatcher.isLatinLetter('5')); 305 assertFalse(PhoneNumberMatcher.isLatinLetter('-')); 306 assertFalse(PhoneNumberMatcher.isLatinLetter('.')); 307 assertFalse(PhoneNumberMatcher.isLatinLetter(' ')); 308 assertFalse(PhoneNumberMatcher.isLatinLetter('\u6211')); // Chinese character 309 assertFalse(PhoneNumberMatcher.isLatinLetter('\u306E')); // Hiragana letter no 310 } 311 testMatchesWithSurroundingLatinChars()312 public void testMatchesWithSurroundingLatinChars() throws Exception { 313 ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>(); 314 possibleOnlyContexts.add(new NumberContext("abc", "def")); 315 possibleOnlyContexts.add(new NumberContext("abc", "")); 316 possibleOnlyContexts.add(new NumberContext("", "def")); 317 // Latin capital letter e with an acute accent. 318 possibleOnlyContexts.add(new NumberContext("\u00C9", "")); 319 // e with an acute accent decomposed (with combining mark). 320 possibleOnlyContexts.add(new NumberContext("e\u0301", "")); 321 322 // Numbers should not be considered valid, if they are surrounded by Latin characters, but 323 // should be considered possible. 324 findMatchesInContexts(possibleOnlyContexts, false, true); 325 } 326 testMoneyNotSeenAsPhoneNumber()327 public void testMoneyNotSeenAsPhoneNumber() throws Exception { 328 ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>(); 329 possibleOnlyContexts.add(new NumberContext("$", "")); 330 possibleOnlyContexts.add(new NumberContext("", "$")); 331 possibleOnlyContexts.add(new NumberContext("\u00A3", "")); // Pound sign 332 possibleOnlyContexts.add(new NumberContext("\u00A5", "")); // Yen sign 333 findMatchesInContexts(possibleOnlyContexts, false, true); 334 } 335 testPercentageNotSeenAsPhoneNumber()336 public void testPercentageNotSeenAsPhoneNumber() throws Exception { 337 ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>(); 338 possibleOnlyContexts.add(new NumberContext("", "%")); 339 // Numbers followed by % should be dropped. 340 findMatchesInContexts(possibleOnlyContexts, false, true); 341 } 342 testPhoneNumberWithLeadingOrTrailingMoneyMatches()343 public void testPhoneNumberWithLeadingOrTrailingMoneyMatches() throws Exception { 344 // Because of the space after the 20 (or before the 100) these dollar amounts should not stop 345 // the actual number from being found. 346 ArrayList<NumberContext> contexts = new ArrayList<NumberContext>(); 347 contexts.add(new NumberContext("$20 ", "")); 348 contexts.add(new NumberContext("", " 100$")); 349 findMatchesInContexts(contexts, true, true); 350 } 351 testMatchesWithSurroundingLatinCharsAndLeadingPunctuation()352 public void testMatchesWithSurroundingLatinCharsAndLeadingPunctuation() throws Exception { 353 // Contexts with trailing characters. Leading characters are okay here since the numbers we will 354 // insert start with punctuation, but trailing characters are still not allowed. 355 ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>(); 356 possibleOnlyContexts.add(new NumberContext("abc", "def")); 357 possibleOnlyContexts.add(new NumberContext("", "def")); 358 possibleOnlyContexts.add(new NumberContext("", "\u00C9")); 359 360 // Numbers should not be considered valid, if they have trailing Latin characters, but should be 361 // considered possible. 362 String numberWithPlus = "+14156667777"; 363 String numberWithBrackets = "(415)6667777"; 364 findMatchesInContexts(possibleOnlyContexts, false, true, RegionCode.US, numberWithPlus); 365 findMatchesInContexts(possibleOnlyContexts, false, true, RegionCode.US, numberWithBrackets); 366 367 ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(); 368 validContexts.add(new NumberContext("abc", "")); 369 validContexts.add(new NumberContext("\u00C9", "")); 370 validContexts.add(new NumberContext("\u00C9", ".")); // Trailing punctuation. 371 validContexts.add(new NumberContext("\u00C9", " def")); // Trailing white-space. 372 373 // Numbers should be considered valid, since they start with punctuation. 374 findMatchesInContexts(validContexts, true, true, RegionCode.US, numberWithPlus); 375 findMatchesInContexts(validContexts, true, true, RegionCode.US, numberWithBrackets); 376 } 377 testMatchesWithSurroundingChineseChars()378 public void testMatchesWithSurroundingChineseChars() throws Exception { 379 ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(); 380 validContexts.add(new NumberContext("\u6211\u7684\u7535\u8BDD\u53F7\u7801\u662F", "")); 381 validContexts.add(new NumberContext("", "\u662F\u6211\u7684\u7535\u8BDD\u53F7\u7801")); 382 validContexts.add(new NumberContext("\u8BF7\u62E8\u6253", "\u6211\u5728\u660E\u5929")); 383 384 // Numbers should be considered valid, since they are surrounded by Chinese. 385 findMatchesInContexts(validContexts, true, true); 386 } 387 testMatchesWithSurroundingPunctuation()388 public void testMatchesWithSurroundingPunctuation() throws Exception { 389 ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(); 390 validContexts.add(new NumberContext("My number-", "")); // At end of text. 391 validContexts.add(new NumberContext("", ".Nice day.")); // At start of text. 392 validContexts.add(new NumberContext("Tel:", ".")); // Punctuation surrounds number. 393 validContexts.add(new NumberContext("Tel: ", " on Saturdays.")); // White-space is also fine. 394 395 // Numbers should be considered valid, since they are surrounded by punctuation. 396 findMatchesInContexts(validContexts, true, true); 397 } 398 testMatchesMultiplePhoneNumbersSeparatedByPhoneNumberPunctuation()399 public void testMatchesMultiplePhoneNumbersSeparatedByPhoneNumberPunctuation() throws Exception { 400 String text = "Call 650-253-4561 -- 455-234-3451"; 401 String region = RegionCode.US; 402 403 PhoneNumber number1 = new PhoneNumber(); 404 number1.setCountryCode(phoneUtil.getCountryCodeForRegion(region)); 405 number1.setNationalNumber(6502534561L); 406 PhoneNumberMatch match1 = new PhoneNumberMatch(5, "650-253-4561", number1); 407 408 PhoneNumber number2 = new PhoneNumber(); 409 number2.setCountryCode(phoneUtil.getCountryCodeForRegion(region)); 410 number2.setNationalNumber(4552343451L); 411 PhoneNumberMatch match2 = new PhoneNumberMatch(21, "455-234-3451", number2); 412 413 Iterator<PhoneNumberMatch> matches = phoneUtil.findNumbers(text, region).iterator(); 414 assertEquals(match1, matches.next()); 415 assertEquals(match2, matches.next()); 416 } 417 testDoesNotMatchMultiplePhoneNumbersSeparatedWithNoWhiteSpace()418 public void testDoesNotMatchMultiplePhoneNumbersSeparatedWithNoWhiteSpace() throws Exception { 419 // No white-space found between numbers - neither is found. 420 String text = "Call 650-253-4561--455-234-3451"; 421 String region = RegionCode.US; 422 423 assertTrue(hasNoMatches(phoneUtil.findNumbers(text, region))); 424 } 425 426 /** 427 * Strings with number-like things that shouldn't be found under any level. 428 */ 429 private static final NumberTest[] IMPOSSIBLE_CASES = { 430 new NumberTest("12345", RegionCode.US), 431 new NumberTest("23456789", RegionCode.US), 432 new NumberTest("234567890112", RegionCode.US), 433 new NumberTest("650+253+1234", RegionCode.US), 434 new NumberTest("3/10/1984", RegionCode.CA), 435 new NumberTest("03/27/2011", RegionCode.US), 436 new NumberTest("31/8/2011", RegionCode.US), 437 new NumberTest("1/12/2011", RegionCode.US), 438 new NumberTest("10/12/82", RegionCode.DE), 439 new NumberTest("650x2531234", RegionCode.US), 440 new NumberTest("2012-01-02 08:00", RegionCode.US), 441 new NumberTest("2012/01/02 08:00", RegionCode.US), 442 new NumberTest("20120102 08:00", RegionCode.US), 443 new NumberTest("2014-04-12 04:04 PM", RegionCode.US), 444 new NumberTest("2014-04-12 04:04 PM", RegionCode.US), 445 new NumberTest("2014-04-12 04:04 PM", RegionCode.US), 446 new NumberTest("2014-04-12 04:04 PM", RegionCode.US), 447 }; 448 449 /** 450 * Strings with number-like things that should only be found under "possible". 451 */ 452 private static final NumberTest[] POSSIBLE_ONLY_CASES = { 453 // US numbers cannot start with 7 in the test metadata to be valid. 454 new NumberTest("7121115678", RegionCode.US), 455 // 'X' should not be found in numbers at leniencies stricter than POSSIBLE, unless it represents 456 // a carrier code or extension. 457 new NumberTest("1650 x 253 - 1234", RegionCode.US), 458 new NumberTest("650 x 253 - 1234", RegionCode.US), 459 new NumberTest("6502531x234", RegionCode.US), 460 new NumberTest("(20) 3346 1234", RegionCode.GB), // Non-optional NP omitted 461 }; 462 463 /** 464 * Strings with number-like things that should only be found up to and including the "valid" 465 * leniency level. 466 */ 467 private static final NumberTest[] VALID_CASES = { 468 new NumberTest("65 02 53 00 00", RegionCode.US), 469 new NumberTest("6502 538365", RegionCode.US), 470 new NumberTest("650//253-1234", RegionCode.US), // 2 slashes are illegal at higher levels 471 new NumberTest("650/253/1234", RegionCode.US), 472 new NumberTest("9002309. 158", RegionCode.US), 473 new NumberTest("12 7/8 - 14 12/34 - 5", RegionCode.US), 474 new NumberTest("12.1 - 23.71 - 23.45", RegionCode.US), 475 new NumberTest("800 234 1 111x1111", RegionCode.US), 476 new NumberTest("1979-2011 100", RegionCode.US), 477 new NumberTest("+494949-4-94", RegionCode.DE), // National number in wrong format 478 new NumberTest("\uFF14\uFF11\uFF15\uFF16\uFF16\uFF16\uFF16-\uFF17\uFF17\uFF17", RegionCode.US), 479 new NumberTest("2012-0102 08", RegionCode.US), // Very strange formatting. 480 new NumberTest("2012-01-02 08", RegionCode.US), 481 // Breakdown assistance number with unexpected formatting. 482 new NumberTest("1800-1-0-10 22", RegionCode.AU), 483 new NumberTest("030-3-2 23 12 34", RegionCode.DE), 484 new NumberTest("03 0 -3 2 23 12 34", RegionCode.DE), 485 new NumberTest("(0)3 0 -3 2 23 12 34", RegionCode.DE), 486 new NumberTest("0 3 0 -3 2 23 12 34", RegionCode.DE), 487 // Fits an alternate pattern, but the leading digits don't match. 488 new NumberTest("+52 332 123 23 23", RegionCode.MX), 489 }; 490 491 /** 492 * Strings with number-like things that should only be found up to and including the 493 * "strict_grouping" leniency level. 494 */ 495 private static final NumberTest[] STRICT_GROUPING_CASES = { 496 new NumberTest("(415) 6667777", RegionCode.US), 497 new NumberTest("415-6667777", RegionCode.US), 498 // Should be found by strict grouping but not exact grouping, as the last two groups are 499 // formatted together as a block. 500 new NumberTest("0800-2491234", RegionCode.DE), 501 // Doesn't match any formatting in the test file, but almost matches an alternate format (the 502 // last two groups have been squashed together here). 503 new NumberTest("0900-1 123123", RegionCode.DE), 504 new NumberTest("(0)900-1 123123", RegionCode.DE), 505 new NumberTest("0 900-1 123123", RegionCode.DE), 506 // NDC also found as part of the country calling code; this shouldn't ruin the grouping 507 // expectations. 508 new NumberTest("+33 3 34 2312", RegionCode.FR), 509 }; 510 511 /** 512 * Strings with number-like things that should be found at all levels. 513 */ 514 private static final NumberTest[] EXACT_GROUPING_CASES = { 515 new NumberTest("\uFF14\uFF11\uFF15\uFF16\uFF16\uFF16\uFF17\uFF17\uFF17\uFF17", RegionCode.US), 516 new NumberTest("\uFF14\uFF11\uFF15-\uFF16\uFF16\uFF16-\uFF17\uFF17\uFF17\uFF17", RegionCode.US), 517 new NumberTest("4156667777", RegionCode.US), 518 new NumberTest("4156667777 x 123", RegionCode.US), 519 new NumberTest("415-666-7777", RegionCode.US), 520 new NumberTest("415/666-7777", RegionCode.US), 521 new NumberTest("415-666-7777 ext. 503", RegionCode.US), 522 new NumberTest("1 415 666 7777 x 123", RegionCode.US), 523 new NumberTest("+1 415-666-7777", RegionCode.US), 524 new NumberTest("+494949 49", RegionCode.DE), 525 new NumberTest("+49-49-34", RegionCode.DE), 526 new NumberTest("+49-4931-49", RegionCode.DE), 527 new NumberTest("04931-49", RegionCode.DE), // With National Prefix 528 new NumberTest("+49-494949", RegionCode.DE), // One group with country code 529 new NumberTest("+49-494949 ext. 49", RegionCode.DE), 530 new NumberTest("+49494949 ext. 49", RegionCode.DE), 531 new NumberTest("0494949", RegionCode.DE), 532 new NumberTest("0494949 ext. 49", RegionCode.DE), 533 new NumberTest("01 (33) 3461 2234", RegionCode.MX), // Optional NP present 534 new NumberTest("(33) 3461 2234", RegionCode.MX), // Optional NP omitted 535 new NumberTest("1800-10-10 22", RegionCode.AU), // Breakdown assistance number. 536 // Doesn't match any formatting in the test file, but matches an alternate format exactly. 537 new NumberTest("0900-1 123 123", RegionCode.DE), 538 new NumberTest("(0)900-1 123 123", RegionCode.DE), 539 new NumberTest("0 900-1 123 123", RegionCode.DE), 540 new NumberTest("+33 3 34 23 12", RegionCode.FR), 541 }; 542 testMatchesWithPossibleLeniency()543 public void testMatchesWithPossibleLeniency() throws Exception { 544 List<NumberTest> testCases = new ArrayList<NumberTest>(); 545 testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES)); 546 testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES)); 547 testCases.addAll(Arrays.asList(VALID_CASES)); 548 testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES)); 549 doTestNumberMatchesForLeniency(testCases, Leniency.POSSIBLE); 550 } 551 testNonMatchesWithPossibleLeniency()552 public void testNonMatchesWithPossibleLeniency() throws Exception { 553 List<NumberTest> testCases = new ArrayList<NumberTest>(); 554 testCases.addAll(Arrays.asList(IMPOSSIBLE_CASES)); 555 doTestNumberNonMatchesForLeniency(testCases, Leniency.POSSIBLE); 556 } 557 testMatchesWithValidLeniency()558 public void testMatchesWithValidLeniency() throws Exception { 559 List<NumberTest> testCases = new ArrayList<NumberTest>(); 560 testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES)); 561 testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES)); 562 testCases.addAll(Arrays.asList(VALID_CASES)); 563 doTestNumberMatchesForLeniency(testCases, Leniency.VALID); 564 } 565 testNonMatchesWithValidLeniency()566 public void testNonMatchesWithValidLeniency() throws Exception { 567 List<NumberTest> testCases = new ArrayList<NumberTest>(); 568 testCases.addAll(Arrays.asList(IMPOSSIBLE_CASES)); 569 testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES)); 570 doTestNumberNonMatchesForLeniency(testCases, Leniency.VALID); 571 } 572 testMatchesWithStrictGroupingLeniency()573 public void testMatchesWithStrictGroupingLeniency() throws Exception { 574 List<NumberTest> testCases = new ArrayList<NumberTest>(); 575 testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES)); 576 testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES)); 577 doTestNumberMatchesForLeniency(testCases, Leniency.STRICT_GROUPING); 578 } 579 testNonMatchesWithStrictGroupLeniency()580 public void testNonMatchesWithStrictGroupLeniency() throws Exception { 581 List<NumberTest> testCases = new ArrayList<NumberTest>(); 582 testCases.addAll(Arrays.asList(IMPOSSIBLE_CASES)); 583 testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES)); 584 testCases.addAll(Arrays.asList(VALID_CASES)); 585 doTestNumberNonMatchesForLeniency(testCases, Leniency.STRICT_GROUPING); 586 } 587 testMatchesWithExactGroupingLeniency()588 public void testMatchesWithExactGroupingLeniency() throws Exception { 589 List<NumberTest> testCases = new ArrayList<NumberTest>(); 590 testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES)); 591 doTestNumberMatchesForLeniency(testCases, Leniency.EXACT_GROUPING); 592 } 593 testNonMatchesExactGroupLeniency()594 public void testNonMatchesExactGroupLeniency() throws Exception { 595 List<NumberTest> testCases = new ArrayList<NumberTest>(); 596 testCases.addAll(Arrays.asList(IMPOSSIBLE_CASES)); 597 testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES)); 598 testCases.addAll(Arrays.asList(VALID_CASES)); 599 testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES)); 600 doTestNumberNonMatchesForLeniency(testCases, Leniency.EXACT_GROUPING); 601 } 602 doTestNumberMatchesForLeniency(List<NumberTest> testCases, Leniency leniency)603 private void doTestNumberMatchesForLeniency(List<NumberTest> testCases, Leniency leniency) { 604 int noMatchFoundCount = 0; 605 int wrongMatchFoundCount = 0; 606 for (NumberTest test : testCases) { 607 Iterator<PhoneNumberMatch> iterator = 608 findNumbersForLeniency(test.rawString, test.region, leniency); 609 PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null; 610 if (match == null) { 611 noMatchFoundCount++; 612 System.err.println("No match found in " + test.toString() + " for leniency: " + leniency); 613 } else { 614 if (!test.rawString.equals(match.rawString())) { 615 wrongMatchFoundCount++; 616 System.err.println("Found wrong match in test " + test.toString() 617 + ". Found " + match.rawString()); 618 } 619 } 620 } 621 assertEquals(0, noMatchFoundCount); 622 assertEquals(0, wrongMatchFoundCount); 623 } 624 doTestNumberNonMatchesForLeniency(List<NumberTest> testCases, Leniency leniency)625 private void doTestNumberNonMatchesForLeniency(List<NumberTest> testCases, Leniency leniency) { 626 int matchFoundCount = 0; 627 for (NumberTest test : testCases) { 628 Iterator<PhoneNumberMatch> iterator = 629 findNumbersForLeniency(test.rawString, test.region, leniency); 630 PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null; 631 if (match != null) { 632 matchFoundCount++; 633 System.err.println("Match found in " + test.toString() + " for leniency: " + leniency); 634 } 635 } 636 assertEquals(0, matchFoundCount); 637 } 638 639 /** 640 * Helper method which tests the contexts provided and ensures that: 641 * -- if isValid is true, they all find a test number inserted in the middle when leniency of 642 * matching is set to VALID; else no test number should be extracted at that leniency level 643 * -- if isPossible is true, they all find a test number inserted in the middle when leniency of 644 * matching is set to POSSIBLE; else no test number should be extracted at that leniency level 645 */ findMatchesInContexts(List<NumberContext> contexts, boolean isValid, boolean isPossible, String region, String number)646 private void findMatchesInContexts(List<NumberContext> contexts, boolean isValid, 647 boolean isPossible, String region, String number) { 648 if (isValid) { 649 doTestInContext(number, region, contexts, Leniency.VALID); 650 } else { 651 for (NumberContext context : contexts) { 652 String text = context.leadingText + number + context.trailingText; 653 assertTrue("Should not have found a number in " + text, 654 hasNoMatches(phoneUtil.findNumbers(text, region))); 655 } 656 } 657 if (isPossible) { 658 doTestInContext(number, region, contexts, Leniency.POSSIBLE); 659 } else { 660 for (NumberContext context : contexts) { 661 String text = context.leadingText + number + context.trailingText; 662 assertTrue("Should not have found a number in " + text, 663 hasNoMatches(phoneUtil.findNumbers(text, region, Leniency.POSSIBLE, 664 Long.MAX_VALUE))); 665 } 666 } 667 } 668 669 /** 670 * Variant of findMatchesInContexts that uses a default number and region. 671 */ findMatchesInContexts(List<NumberContext> contexts, boolean isValid, boolean isPossible)672 private void findMatchesInContexts(List<NumberContext> contexts, boolean isValid, 673 boolean isPossible) { 674 String region = RegionCode.US; 675 String number = "415-666-7777"; 676 677 findMatchesInContexts(contexts, isValid, isPossible, region, number); 678 } 679 testNonMatchingBracketsAreInvalid()680 public void testNonMatchingBracketsAreInvalid() throws Exception { 681 // The digits up to the ", " form a valid US number, but it shouldn't be matched as one since 682 // there was a non-matching bracket present. 683 assertTrue(hasNoMatches(phoneUtil.findNumbers( 684 "80.585 [79.964, 81.191]", RegionCode.US))); 685 686 // The trailing "]" is thrown away before parsing, so the resultant number, while a valid US 687 // number, does not have matching brackets. 688 assertTrue(hasNoMatches(phoneUtil.findNumbers( 689 "80.585 [79.964]", RegionCode.US))); 690 691 assertTrue(hasNoMatches(phoneUtil.findNumbers( 692 "80.585 ((79.964)", RegionCode.US))); 693 694 // This case has too many sets of brackets to be valid. 695 assertTrue(hasNoMatches(phoneUtil.findNumbers( 696 "(80).(585) (79).(9)64", RegionCode.US))); 697 } 698 testNoMatchIfRegionIsNull()699 public void testNoMatchIfRegionIsNull() throws Exception { 700 // Fail on non-international prefix if region code is null. 701 assertTrue(hasNoMatches(phoneUtil.findNumbers( 702 "Random text body - number is 0331 6005, see you there", null))); 703 } 704 testNoMatchInEmptyString()705 public void testNoMatchInEmptyString() throws Exception { 706 assertTrue(hasNoMatches(phoneUtil.findNumbers("", RegionCode.US))); 707 assertTrue(hasNoMatches(phoneUtil.findNumbers(" ", RegionCode.US))); 708 } 709 testNoMatchIfNoNumber()710 public void testNoMatchIfNoNumber() throws Exception { 711 assertTrue(hasNoMatches(phoneUtil.findNumbers( 712 "Random text body - number is foobar, see you there", RegionCode.US))); 713 } 714 testSequences()715 public void testSequences() throws Exception { 716 // Test multiple occurrences. 717 String text = "Call 033316005 or 032316005!"; 718 String region = RegionCode.NZ; 719 720 PhoneNumber number1 = new PhoneNumber(); 721 number1.setCountryCode(phoneUtil.getCountryCodeForRegion(region)); 722 number1.setNationalNumber(33316005); 723 PhoneNumberMatch match1 = new PhoneNumberMatch(5, "033316005", number1); 724 725 PhoneNumber number2 = new PhoneNumber(); 726 number2.setCountryCode(phoneUtil.getCountryCodeForRegion(region)); 727 number2.setNationalNumber(32316005); 728 PhoneNumberMatch match2 = new PhoneNumberMatch(19, "032316005", number2); 729 730 Iterator<PhoneNumberMatch> matches = 731 phoneUtil.findNumbers(text, region, Leniency.POSSIBLE, Long.MAX_VALUE).iterator(); 732 733 assertEquals(match1, matches.next()); 734 assertEquals(match2, matches.next()); 735 } 736 testNullInput()737 public void testNullInput() throws Exception { 738 assertTrue(hasNoMatches(phoneUtil.findNumbers(null, RegionCode.US))); 739 assertTrue(hasNoMatches(phoneUtil.findNumbers(null, null))); 740 } 741 testMaxMatches()742 public void testMaxMatches() throws Exception { 743 // Set up text with 100 valid phone numbers. 744 StringBuilder numbers = new StringBuilder(); 745 for (int i = 0; i < 100; i++) { 746 numbers.append("My info: 415-666-7777,"); 747 } 748 749 // Matches all 100. Max only applies to failed cases. 750 List<PhoneNumber> expected = new ArrayList<PhoneNumber>(100); 751 PhoneNumber number = phoneUtil.parse("+14156667777", null); 752 for (int i = 0; i < 100; i++) { 753 expected.add(number); 754 } 755 756 Iterable<PhoneNumberMatch> iterable = 757 phoneUtil.findNumbers(numbers.toString(), RegionCode.US, Leniency.VALID, 10); 758 List<PhoneNumber> actual = new ArrayList<PhoneNumber>(100); 759 for (PhoneNumberMatch match : iterable) { 760 actual.add(match.number()); 761 } 762 assertEquals(expected, actual); 763 } 764 testMaxMatchesInvalid()765 public void testMaxMatchesInvalid() throws Exception { 766 // Set up text with 10 invalid phone numbers followed by 100 valid. 767 StringBuilder numbers = new StringBuilder(); 768 for (int i = 0; i < 10; i++) { 769 numbers.append("My address 949-8945-0"); 770 } 771 for (int i = 0; i < 100; i++) { 772 numbers.append("My info: 415-666-7777,"); 773 } 774 775 Iterable<PhoneNumberMatch> iterable = 776 phoneUtil.findNumbers(numbers.toString(), RegionCode.US, Leniency.VALID, 10); 777 assertFalse(iterable.iterator().hasNext()); 778 } 779 testMaxMatchesMixed()780 public void testMaxMatchesMixed() throws Exception { 781 // Set up text with 100 valid numbers inside an invalid number. 782 StringBuilder numbers = new StringBuilder(); 783 for (int i = 0; i < 100; i++) { 784 numbers.append("My info: 415-666-7777 123 fake street"); 785 } 786 787 // Only matches the first 10 despite there being 100 numbers due to max matches. 788 List<PhoneNumber> expected = new ArrayList<PhoneNumber>(100); 789 PhoneNumber number = phoneUtil.parse("+14156667777", null); 790 for (int i = 0; i < 10; i++) { 791 expected.add(number); 792 } 793 794 Iterable<PhoneNumberMatch> iterable = 795 phoneUtil.findNumbers(numbers.toString(), RegionCode.US, Leniency.VALID, 10); 796 List<PhoneNumber> actual = new ArrayList<PhoneNumber>(100); 797 for (PhoneNumberMatch match : iterable) { 798 actual.add(match.number()); 799 } 800 assertEquals(expected, actual); 801 } 802 testNonPlusPrefixedNumbersNotFoundForInvalidRegion()803 public void testNonPlusPrefixedNumbersNotFoundForInvalidRegion() throws Exception { 804 // Does not start with a "+", we won't match it. 805 Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("1 456 764 156", RegionCode.ZZ); 806 Iterator<PhoneNumberMatch> iterator = iterable.iterator(); 807 808 assertFalse(iterator.hasNext()); 809 try { 810 iterator.next(); 811 fail("Violation of the Iterator contract."); 812 } catch (NoSuchElementException e) { /* Success */ } 813 assertFalse(iterator.hasNext()); 814 } 815 testEmptyIteration()816 public void testEmptyIteration() throws Exception { 817 Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("", RegionCode.ZZ); 818 Iterator<PhoneNumberMatch> iterator = iterable.iterator(); 819 820 assertFalse(iterator.hasNext()); 821 assertFalse(iterator.hasNext()); 822 try { 823 iterator.next(); 824 fail("Violation of the Iterator contract."); 825 } catch (NoSuchElementException e) { /* Success */ } 826 assertFalse(iterator.hasNext()); 827 } 828 testSingleIteration()829 public void testSingleIteration() throws Exception { 830 Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("+14156667777", RegionCode.ZZ); 831 832 // With hasNext() -> next(). 833 Iterator<PhoneNumberMatch> iterator = iterable.iterator(); 834 // Double hasNext() to ensure it does not advance. 835 assertTrue(iterator.hasNext()); 836 assertTrue(iterator.hasNext()); 837 assertNotNull(iterator.next()); 838 assertFalse(iterator.hasNext()); 839 try { 840 iterator.next(); 841 fail("Violation of the Iterator contract."); 842 } catch (NoSuchElementException e) { /* Success */ } 843 assertFalse(iterator.hasNext()); 844 845 // With next() only. 846 iterator = iterable.iterator(); 847 assertNotNull(iterator.next()); 848 try { 849 iterator.next(); 850 fail("Violation of the Iterator contract."); 851 } catch (NoSuchElementException e) { /* Success */ } 852 } 853 testDoubleIteration()854 public void testDoubleIteration() throws Exception { 855 Iterable<PhoneNumberMatch> iterable = 856 phoneUtil.findNumbers("+14156667777 foobar +14156667777 ", RegionCode.ZZ); 857 858 // With hasNext() -> next(). 859 Iterator<PhoneNumberMatch> iterator = iterable.iterator(); 860 // Double hasNext() to ensure it does not advance. 861 assertTrue(iterator.hasNext()); 862 assertTrue(iterator.hasNext()); 863 assertNotNull(iterator.next()); 864 assertTrue(iterator.hasNext()); 865 assertTrue(iterator.hasNext()); 866 assertNotNull(iterator.next()); 867 assertFalse(iterator.hasNext()); 868 try { 869 iterator.next(); 870 fail("Violation of the Iterator contract."); 871 } catch (NoSuchElementException e) { /* Success */ } 872 assertFalse(iterator.hasNext()); 873 874 // With next() only. 875 iterator = iterable.iterator(); 876 assertNotNull(iterator.next()); 877 assertNotNull(iterator.next()); 878 try { 879 iterator.next(); 880 fail("Violation of the Iterator contract."); 881 } catch (NoSuchElementException e) { /* Success */ } 882 } 883 884 /** 885 * Ensures that {@link Iterator#remove()} is not supported and that calling it does not 886 * change iteration behavior. 887 */ testRemovalNotSupported()888 public void testRemovalNotSupported() throws Exception { 889 Iterable<PhoneNumberMatch> iterable = phoneUtil.findNumbers("+14156667777", RegionCode.ZZ); 890 891 Iterator<PhoneNumberMatch> iterator = iterable.iterator(); 892 try { 893 iterator.remove(); 894 fail("Iterator must not support remove."); 895 } catch (UnsupportedOperationException e) { /* success */ } 896 897 assertTrue(iterator.hasNext()); 898 899 try { 900 iterator.remove(); 901 fail("Iterator must not support remove."); 902 } catch (UnsupportedOperationException e) { /* success */ } 903 904 assertNotNull(iterator.next()); 905 906 try { 907 iterator.remove(); 908 fail("Iterator must not support remove."); 909 } catch (UnsupportedOperationException e) { /* success */ } 910 911 assertFalse(iterator.hasNext()); 912 } 913 914 /** 915 * Asserts that the expected match is non-null, and that the raw string and expected 916 * proto buffer are set appropriately. 917 */ assertMatchProperties( PhoneNumberMatch match, String text, String number, String region)918 private void assertMatchProperties( 919 PhoneNumberMatch match, String text, String number, String region) throws Exception { 920 PhoneNumber expectedResult = phoneUtil.parse(number, region); 921 assertNotNull("Did not find a number in '" + text + "'; expected " + number, match); 922 assertEquals(expectedResult, match.number()); 923 assertEquals(number, match.rawString()); 924 } 925 926 /** 927 * Asserts that another number can be found in {@code text} starting at {@code index}, and that 928 * its corresponding range is {@code [start, end)}. 929 */ assertEqualRange(CharSequence text, int index, int start, int end)930 private void assertEqualRange(CharSequence text, int index, int start, int end) { 931 CharSequence sub = text.subSequence(index, text.length()); 932 Iterator<PhoneNumberMatch> matches = 933 phoneUtil.findNumbers(sub, RegionCode.NZ, Leniency.POSSIBLE, Long.MAX_VALUE).iterator(); 934 assertTrue(matches.hasNext()); 935 PhoneNumberMatch match = matches.next(); 936 assertEquals(start - index, match.start()); 937 assertEquals(end - index, match.end()); 938 assertEquals(sub.subSequence(match.start(), match.end()).toString(), match.rawString()); 939 } 940 941 /** 942 * Tests numbers found by {@link PhoneNumberUtil#findNumbers(CharSequence, String)} in various 943 * textual contexts. 944 * 945 * @param number the number to test and the corresponding region code to use 946 */ doTestFindInContext(String number, String defaultCountry)947 private void doTestFindInContext(String number, String defaultCountry) throws Exception { 948 findPossibleInContext(number, defaultCountry); 949 950 PhoneNumber parsed = phoneUtil.parse(number, defaultCountry); 951 if (phoneUtil.isValidNumber(parsed)) { 952 findValidInContext(number, defaultCountry); 953 } 954 } 955 956 /** 957 * Tests valid numbers in contexts that should pass for {@link Leniency#POSSIBLE}. 958 */ findPossibleInContext(String number, String defaultCountry)959 private void findPossibleInContext(String number, String defaultCountry) { 960 ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(); 961 contextPairs.add(new NumberContext("", "")); // no context 962 contextPairs.add(new NumberContext(" ", "\t")); // whitespace only 963 contextPairs.add(new NumberContext("Hello ", "")); // no context at end 964 contextPairs.add(new NumberContext("", " to call me!")); // no context at start 965 contextPairs.add(new NumberContext("Hi there, call ", " to reach me!")); // no context at start 966 contextPairs.add(new NumberContext("Hi there, call ", ", or don't")); // with commas 967 // Three examples without whitespace around the number. 968 contextPairs.add(new NumberContext("Hi call", "")); 969 contextPairs.add(new NumberContext("", "forme")); 970 contextPairs.add(new NumberContext("Hi call", "forme")); 971 // With other small numbers. 972 contextPairs.add(new NumberContext("It's cheap! Call ", " before 6:30")); 973 // With a second number later. 974 contextPairs.add(new NumberContext("Call ", " or +1800-123-4567!")); 975 contextPairs.add(new NumberContext("Call me on June 2 at", "")); // with a Month-Day date 976 // With publication pages. 977 contextPairs.add(new NumberContext( 978 "As quoted by Alfonso 12-15 (2009), you may call me at ", "")); 979 contextPairs.add(new NumberContext( 980 "As quoted by Alfonso et al. 12-15 (2009), you may call me at ", "")); 981 // With dates, written in the American style. 982 contextPairs.add(new NumberContext( 983 "As I said on 03/10/2011, you may call me at ", "")); 984 // With trailing numbers after a comma. The 45 should not be considered an extension. 985 contextPairs.add(new NumberContext("", ", 45 days a year")); 986 // When matching we don't consider semicolon along with legitimate extension symbol to indicate 987 // an extension. The 7246433 should not be considered an extension. 988 contextPairs.add(new NumberContext("", ";x 7246433")); 989 // With a postfix stripped off as it looks like the start of another number. 990 contextPairs.add(new NumberContext("Call ", "/x12 more")); 991 992 doTestInContext(number, defaultCountry, contextPairs, Leniency.POSSIBLE); 993 } 994 995 /** 996 * Tests valid numbers in contexts that fail for {@link Leniency#POSSIBLE} but are valid for 997 * {@link Leniency#VALID}. 998 */ findValidInContext(String number, String defaultCountry)999 private void findValidInContext(String number, String defaultCountry) { 1000 ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(); 1001 // With other small numbers. 1002 contextPairs.add(new NumberContext("It's only 9.99! Call ", " to buy")); 1003 // With a number Day.Month.Year date. 1004 contextPairs.add(new NumberContext("Call me on 21.6.1984 at ", "")); 1005 // With a number Month/Day date. 1006 contextPairs.add(new NumberContext("Call me on 06/21 at ", "")); 1007 // With a number Day.Month date. 1008 contextPairs.add(new NumberContext("Call me on 21.6. at ", "")); 1009 // With a number Month/Day/Year date. 1010 contextPairs.add(new NumberContext("Call me on 06/21/84 at ", "")); 1011 1012 doTestInContext(number, defaultCountry, contextPairs, Leniency.VALID); 1013 } 1014 doTestInContext(String number, String defaultCountry, List<NumberContext> contextPairs, Leniency leniency)1015 private void doTestInContext(String number, String defaultCountry, 1016 List<NumberContext> contextPairs, Leniency leniency) { 1017 for (NumberContext context : contextPairs) { 1018 String prefix = context.leadingText; 1019 String text = prefix + number + context.trailingText; 1020 1021 int start = prefix.length(); 1022 int end = start + number.length(); 1023 Iterator<PhoneNumberMatch> iterator = 1024 phoneUtil.findNumbers(text, defaultCountry, leniency, Long.MAX_VALUE).iterator(); 1025 1026 PhoneNumberMatch match = iterator.hasNext() ? iterator.next() : null; 1027 assertNotNull("Did not find a number in '" + text + "'; expected '" + number + "'", match); 1028 1029 CharSequence extracted = text.subSequence(match.start(), match.end()); 1030 assertTrue("Unexpected phone region in '" + text + "'; extracted '" + extracted + "'", 1031 start == match.start() && end == match.end()); 1032 assertTrue(number.contentEquals(extracted)); 1033 assertTrue(match.rawString().contentEquals(extracted)); 1034 1035 ensureTermination(text, defaultCountry, leniency); 1036 } 1037 } 1038 1039 /** 1040 * Exhaustively searches for phone numbers from each index within {@code text} to test that 1041 * finding matches always terminates. 1042 */ ensureTermination(String text, String defaultCountry, Leniency leniency)1043 private void ensureTermination(String text, String defaultCountry, Leniency leniency) { 1044 for (int index = 0; index <= text.length(); index++) { 1045 String sub = text.substring(index); 1046 StringBuilder matches = new StringBuilder(); 1047 // Iterates over all matches. 1048 for (PhoneNumberMatch match : 1049 phoneUtil.findNumbers(sub, defaultCountry, leniency, Long.MAX_VALUE)) { 1050 matches.append(", ").append(match.toString()); 1051 } 1052 } 1053 } 1054 findNumbersForLeniency( String text, String defaultCountry, Leniency leniency)1055 private Iterator<PhoneNumberMatch> findNumbersForLeniency( 1056 String text, String defaultCountry, Leniency leniency) { 1057 return phoneUtil.findNumbers(text, defaultCountry, leniency, Long.MAX_VALUE).iterator(); 1058 } 1059 hasNoMatches(Iterable<PhoneNumberMatch> iterable)1060 private boolean hasNoMatches(Iterable<PhoneNumberMatch> iterable) { 1061 return !iterable.iterator().hasNext(); 1062 } 1063 1064 /** 1065 * Small class that holds the context of the number we are testing against. The test will 1066 * insert the phone number to be found between leadingText and trailingText. 1067 */ 1068 private static class NumberContext { 1069 final String leadingText; 1070 final String trailingText; 1071 NumberContext(String leadingText, String trailingText)1072 NumberContext(String leadingText, String trailingText) { 1073 this.leadingText = leadingText; 1074 this.trailingText = trailingText; 1075 } 1076 } 1077 1078 /** 1079 * Small class that holds the number we want to test and the region for which it should be valid. 1080 */ 1081 private static class NumberTest { 1082 final String rawString; 1083 final String region; 1084 NumberTest(String rawString, String regionCode)1085 NumberTest(String rawString, String regionCode) { 1086 this.rawString = rawString; 1087 this.region = regionCode; 1088 } 1089 1090 @Override toString()1091 public String toString() { 1092 return rawString + " (" + region.toString() + ")"; 1093 } 1094 } 1095 } 1096