• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()}. */
testFindNationalNumber()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()}. */
testFindWithInternationalPrefixes()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()}. */
testFindWithLeadingZero()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()}. */
testFindNationalNumberArgentina()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()}. */
testFindWithXInNumber()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()}. */
testFindNumbersMexico()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()}. */
testFindNumbersWithPlusWithNoRegion()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()}. */
testFindExtensions()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 
testFindInterspersedWithSpace()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    */
testIntermediateParsePositions()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 
testMatchWithSurroundingZipcodes()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 
testIsLatinLetter()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 
testMatchesWithSurroundingLatinChars()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 
testMoneyNotSeenAsPhoneNumber()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 
testPercentageNotSeenAsPhoneNumber()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 
testPhoneNumberWithLeadingOrTrailingMoneyMatches()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 
testMatchesWithSurroundingLatinCharsAndLeadingPunctuation()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 
testMatchesWithSurroundingChineseChars()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 
testMatchesWithSurroundingPunctuation()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 
testMatchesMultiplePhoneNumbersSeparatedByPhoneNumberPunctuation()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 
testDoesNotMatchMultiplePhoneNumbersSeparatedWithNoWhiteSpace()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 
testMatchesWithPossibleLeniency()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 
testNonMatchesWithPossibleLeniency()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 
testMatchesWithValidLeniency()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 
testNonMatchesWithValidLeniency()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 
testMatchesWithStrictGroupingLeniency()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 
testNonMatchesWithStrictGroupLeniency()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 
testMatchesWithExactGroupingLeniency()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 
testNonMatchesExactGroupLeniency()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 
doTestNumberMatchesForLeniency(List<NumberTest> testCases, PhoneNumberUtil.Leniency leniency)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 
doTestNumberNonMatchesForLeniency(List<NumberTest> testCases, PhoneNumberUtil.Leniency leniency)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    */
findMatchesInContexts(List<NumberContext> contexts, boolean isValid, boolean isPossible, String region, String number)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    */
findMatchesInContexts(List<NumberContext> contexts, boolean isValid, boolean isPossible)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 
testNonMatchingBracketsAreInvalid()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 
testNoMatchIfRegionIsNull()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 
testNoMatchInEmptyString()623   public void testNoMatchInEmptyString() throws Exception {
624     assertTrue(hasNoMatches(phoneUtil.findNumbers("", RegionCode.US)));
625     assertTrue(hasNoMatches(phoneUtil.findNumbers("  ", RegionCode.US)));
626   }
627 
testNoMatchIfNoNumber()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 
testSequences()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 
testNullInput()655   public void testNullInput() throws Exception {
656     assertTrue(hasNoMatches(phoneUtil.findNumbers(null, RegionCode.US)));
657     assertTrue(hasNoMatches(phoneUtil.findNumbers(null, null)));
658   }
659 
testMaxMatches()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 
testMaxMatchesInvalid()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 
testMaxMatchesMixed()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 
testNonPlusPrefixedNumbersNotFoundForInvalidRegion()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 
testEmptyIteration()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 
testSingleIteration()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 
testDoubleIteration()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    */
testRemovalNotSupported()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    */
assertEqualRange(CharSequence text, int index, int start, int end)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    */
doTestFindInContext(String number, String defaultCountry)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    */
findPossibleInContext(String number, String defaultCountry)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    */
findValidInContext(String number, String defaultCountry)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 
doTestInContext(String number, String defaultCountry, List<NumberContext> contextPairs, Leniency leniency)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    */
ensureTermination(String text, String defaultCountry, Leniency leniency)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 
findNumbersForLeniency( String text, String defaultCountry, PhoneNumberUtil.Leniency leniency)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 
hasNoMatches(Iterable<PhoneNumberMatch> iterable)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 
NumberContext(String leadingText, String trailingText)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 
NumberTest(String rawString, String regionCode)988     NumberTest(String rawString, String regionCode) {
989       this.rawString = rawString;
990       this.region = regionCode;
991     }
992 
993     @Override
toString()994     public String toString() {
995       return rawString + " (" + region.toString() + ")";
996     }
997   }
998 }
999