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