• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Libphonenumber Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.i18n.phonenumbers;
18 
19 import com.google.i18n.phonenumbers.internal.MatcherApi;
20 import com.google.i18n.phonenumbers.internal.RegexBasedMatcher;
21 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
22 import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
23 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
24 
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.logging.Level;
33 import java.util.logging.Logger;
34 
35 /**
36  * Methods for getting information about short phone numbers, such as short codes and emergency
37  * numbers. Note that most commercial short numbers are not handled here, but by the
38  * {@link PhoneNumberUtil}.
39  *
40  * @author Shaopeng Jia
41  * @author David Yonge-Mallo
42  */
43 public class ShortNumberInfo {
44   private static final Logger logger = Logger.getLogger(ShortNumberInfo.class.getName());
45 
46   private static final ShortNumberInfo INSTANCE =
47       new ShortNumberInfo(RegexBasedMatcher.create());
48 
49   // In these countries, if extra digits are added to an emergency number, it no longer connects
50   // to the emergency service.
51   private static final Set<String> REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT =
52       new HashSet<String>();
53   static {
54     REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("BR");
55     REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("CL");
56     REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.add("NI");
57   }
58 
59   /** Cost categories of short numbers. */
60   public enum ShortNumberCost {
61     TOLL_FREE,
62     STANDARD_RATE,
63     PREMIUM_RATE,
64     UNKNOWN_COST;
65   }
66 
67   /** Returns the singleton instance of the ShortNumberInfo. */
getInstance()68   public static ShortNumberInfo getInstance() {
69     return INSTANCE;
70   }
71 
72   // MatcherApi supports the basic matching method for checking if a given national number matches
73   // a national number pattern defined in the given {@code PhoneNumberDesc}.
74   private final MatcherApi matcherApi;
75 
76   // A mapping from a country calling code to the region codes which denote the region represented
77   // by that country calling code. In the case of multiple regions sharing a calling code, such as
78   // the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
79   // first.
80   private final Map<Integer, List<String>> countryCallingCodeToRegionCodeMap;
81 
82   // @VisibleForTesting
ShortNumberInfo(MatcherApi matcherApi)83   ShortNumberInfo(MatcherApi matcherApi) {
84     this.matcherApi = matcherApi;
85     // TODO: Create ShortNumberInfo for a given map
86     this.countryCallingCodeToRegionCodeMap =
87         CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap();
88   }
89 
90   /**
91    * Returns a list with the region codes that match the specific country calling code. For
92    * non-geographical country calling codes, the region code 001 is returned. Also, in the case
93    * of no region code being found, an empty list is returned.
94    */
getRegionCodesForCountryCode(int countryCallingCode)95   private List<String> getRegionCodesForCountryCode(int countryCallingCode) {
96     List<String> regionCodes = countryCallingCodeToRegionCodeMap.get(countryCallingCode);
97     return Collections.unmodifiableList(regionCodes == null ? new ArrayList<String>(0)
98                                                             : regionCodes);
99   }
100 
101   /**
102    * Helper method to check that the country calling code of the number matches the region it's
103    * being dialed from.
104    */
regionDialingFromMatchesNumber(PhoneNumber number, String regionDialingFrom)105   private boolean regionDialingFromMatchesNumber(PhoneNumber number,
106       String regionDialingFrom) {
107     List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
108     return regionCodes.contains(regionDialingFrom);
109   }
110 
111   /**
112    * Check whether a short number is a possible number when dialed from the given region. This
113    * provides a more lenient check than {@link #isValidShortNumberForRegion}.
114    *
115    * @param number the short number to check
116    * @param regionDialingFrom the region from which the number is dialed
117    * @return whether the number is a possible short number
118    */
isPossibleShortNumberForRegion(PhoneNumber number, String regionDialingFrom)119   public boolean isPossibleShortNumberForRegion(PhoneNumber number, String regionDialingFrom) {
120     if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
121       return false;
122     }
123     PhoneMetadata phoneMetadata =
124         MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
125     if (phoneMetadata == null) {
126       return false;
127     }
128     int numberLength = getNationalSignificantNumber(number).length();
129     return phoneMetadata.getGeneralDesc().getPossibleLengthList().contains(numberLength);
130   }
131 
132   /**
133    * Check whether a short number is a possible number. If a country calling code is shared by
134    * multiple regions, this returns true if it's possible in any of them. This provides a more
135    * lenient check than {@link #isValidShortNumber}. See {@link
136    * #isPossibleShortNumberForRegion(PhoneNumber, String)} for details.
137    *
138    * @param number the short number to check
139    * @return whether the number is a possible short number
140    */
isPossibleShortNumber(PhoneNumber number)141   public boolean isPossibleShortNumber(PhoneNumber number) {
142     List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
143     int shortNumberLength = getNationalSignificantNumber(number).length();
144     for (String region : regionCodes) {
145       PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(region);
146       if (phoneMetadata == null) {
147         continue;
148       }
149       if (phoneMetadata.getGeneralDesc().getPossibleLengthList().contains(shortNumberLength)) {
150         return true;
151       }
152     }
153     return false;
154   }
155 
156   /**
157    * Tests whether a short number matches a valid pattern in a region. Note that this doesn't verify
158    * the number is actually in use, which is impossible to tell by just looking at the number
159    * itself.
160    *
161    * @param number the short number for which we want to test the validity
162    * @param regionDialingFrom the region from which the number is dialed
163    * @return whether the short number matches a valid pattern
164    */
isValidShortNumberForRegion(PhoneNumber number, String regionDialingFrom)165   public boolean isValidShortNumberForRegion(PhoneNumber number, String regionDialingFrom) {
166     if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
167       return false;
168     }
169     PhoneMetadata phoneMetadata =
170         MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
171     if (phoneMetadata == null) {
172       return false;
173     }
174     String shortNumber = getNationalSignificantNumber(number);
175     PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc();
176     if (!matchesPossibleNumberAndNationalNumber(shortNumber, generalDesc)) {
177       return false;
178     }
179     PhoneNumberDesc shortNumberDesc = phoneMetadata.getShortCode();
180     return matchesPossibleNumberAndNationalNumber(shortNumber, shortNumberDesc);
181   }
182 
183   /**
184    * Tests whether a short number matches a valid pattern. If a country calling code is shared by
185    * multiple regions, this returns true if it's valid in any of them. Note that this doesn't verify
186    * the number is actually in use, which is impossible to tell by just looking at the number
187    * itself. See {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details.
188    *
189    * @param number the short number for which we want to test the validity
190    * @return whether the short number matches a valid pattern
191    */
isValidShortNumber(PhoneNumber number)192   public boolean isValidShortNumber(PhoneNumber number) {
193     List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
194     String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes);
195     if (regionCodes.size() > 1 && regionCode != null) {
196       // If a matching region had been found for the phone number from among two or more regions,
197       // then we have already implicitly verified its validity for that region.
198       return true;
199     }
200     return isValidShortNumberForRegion(number, regionCode);
201   }
202 
203   /**
204    * Gets the expected cost category of a short number when dialed from a region (however, nothing
205    * is implied about its validity). If it is important that the number is valid, then its validity
206    * must first be checked using {@link #isValidShortNumberForRegion}. Note that emergency numbers
207    * are always considered toll-free. Example usage:
208    * <pre>{@code
209    * // The region for which the number was parsed and the region we subsequently check against
210    * // need not be the same. Here we parse the number in the US and check it for Canada.
211    * PhoneNumber number = phoneUtil.parse("110", "US");
212    * ...
213    * String regionCode = "CA";
214    * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance();
215    * if (shortInfo.isValidShortNumberForRegion(shortNumber, regionCode)) {
216    *   ShortNumberCost cost = shortInfo.getExpectedCostForRegion(number, regionCode);
217    *   // Do something with the cost information here.
218    * }}</pre>
219    *
220    * @param number the short number for which we want to know the expected cost category
221    * @param regionDialingFrom the region from which the number is dialed
222    * @return the expected cost category for that region of the short number. Returns UNKNOWN_COST if
223    *     the number does not match a cost category. Note that an invalid number may match any cost
224    *     category.
225    */
getExpectedCostForRegion(PhoneNumber number, String regionDialingFrom)226   public ShortNumberCost getExpectedCostForRegion(PhoneNumber number, String regionDialingFrom) {
227     if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
228       return ShortNumberCost.UNKNOWN_COST;
229     }
230     // Note that regionDialingFrom may be null, in which case phoneMetadata will also be null.
231     PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(
232         regionDialingFrom);
233     if (phoneMetadata == null) {
234       return ShortNumberCost.UNKNOWN_COST;
235     }
236 
237     String shortNumber = getNationalSignificantNumber(number);
238 
239     // The possible lengths are not present for a particular sub-type if they match the general
240     // description; for this reason, we check the possible lengths against the general description
241     // first to allow an early exit if possible.
242     if (!phoneMetadata.getGeneralDesc().getPossibleLengthList().contains(shortNumber.length())) {
243       return ShortNumberCost.UNKNOWN_COST;
244     }
245 
246     // The cost categories are tested in order of decreasing expense, since if for some reason the
247     // patterns overlap the most expensive matching cost category should be returned.
248     if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getPremiumRate())) {
249       return ShortNumberCost.PREMIUM_RATE;
250     }
251     if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getStandardRate())) {
252       return ShortNumberCost.STANDARD_RATE;
253     }
254     if (matchesPossibleNumberAndNationalNumber(shortNumber, phoneMetadata.getTollFree())) {
255       return ShortNumberCost.TOLL_FREE;
256     }
257     if (isEmergencyNumber(shortNumber, regionDialingFrom)) {
258       // Emergency numbers are implicitly toll-free.
259       return ShortNumberCost.TOLL_FREE;
260     }
261     return ShortNumberCost.UNKNOWN_COST;
262   }
263 
264   /**
265    * Gets the expected cost category of a short number (however, nothing is implied about its
266    * validity). If the country calling code is unique to a region, this method behaves exactly the
267    * same as {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the country
268    * calling code is shared by multiple regions, then it returns the highest cost in the sequence
269    * PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, TOLL_FREE. The reason for the position of
270    * UNKNOWN_COST in this order is that if a number is UNKNOWN_COST in one region but STANDARD_RATE
271    * or TOLL_FREE in another, its expected cost cannot be estimated as one of the latter since it
272    * might be a PREMIUM_RATE number.
273    * <p>
274    * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, the expected
275    * cost returned by this method will be STANDARD_RATE, since the NANPA countries share the same
276    * country calling code.
277    * <p>
278    * Note: If the region from which the number is dialed is known, it is highly preferable to call
279    * {@link #getExpectedCostForRegion(PhoneNumber, String)} instead.
280    *
281    * @param number the short number for which we want to know the expected cost category
282    * @return the highest expected cost category of the short number in the region(s) with the given
283    *     country calling code
284    */
getExpectedCost(PhoneNumber number)285   public ShortNumberCost getExpectedCost(PhoneNumber number) {
286     List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
287     if (regionCodes.size() == 0) {
288       return ShortNumberCost.UNKNOWN_COST;
289     }
290     if (regionCodes.size() == 1) {
291       return getExpectedCostForRegion(number, regionCodes.get(0));
292     }
293     ShortNumberCost cost = ShortNumberCost.TOLL_FREE;
294     for (String regionCode : regionCodes) {
295       ShortNumberCost costForRegion = getExpectedCostForRegion(number, regionCode);
296       switch (costForRegion) {
297         case PREMIUM_RATE:
298           return ShortNumberCost.PREMIUM_RATE;
299         case UNKNOWN_COST:
300           cost = ShortNumberCost.UNKNOWN_COST;
301           break;
302         case STANDARD_RATE:
303           if (cost != ShortNumberCost.UNKNOWN_COST) {
304             cost = ShortNumberCost.STANDARD_RATE;
305           }
306           break;
307         case TOLL_FREE:
308           // Do nothing.
309           break;
310         default:
311           logger.log(Level.SEVERE, "Unrecognised cost for region: " + costForRegion);
312       }
313     }
314     return cost;
315   }
316 
317   // Helper method to get the region code for a given phone number, from a list of possible region
318   // codes. If the list contains more than one region, the first region for which the number is
319   // valid is returned.
getRegionCodeForShortNumberFromRegionList(PhoneNumber number, List<String> regionCodes)320   private String getRegionCodeForShortNumberFromRegionList(PhoneNumber number,
321                                                            List<String> regionCodes) {
322     if (regionCodes.size() == 0) {
323       return null;
324     } else if (regionCodes.size() == 1) {
325       return regionCodes.get(0);
326     }
327     String nationalNumber = getNationalSignificantNumber(number);
328     for (String regionCode : regionCodes) {
329       PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
330       if (phoneMetadata != null
331           && matchesPossibleNumberAndNationalNumber(nationalNumber, phoneMetadata.getShortCode())) {
332         // The number is valid for this region.
333         return regionCode;
334       }
335     }
336     return null;
337   }
338 
339   /**
340    * Convenience method to get a list of what regions the library has metadata for.
341    */
getSupportedRegions()342   Set<String> getSupportedRegions() {
343     return MetadataManager.getSupportedShortNumberRegions();
344   }
345 
346   /**
347    * Gets a valid short number for the specified region.
348    *
349    * @param regionCode the region for which an example short number is needed
350    * @return a valid short number for the specified region. Returns an empty string when the
351    *     metadata does not contain such information.
352    */
353   // @VisibleForTesting
getExampleShortNumber(String regionCode)354   String getExampleShortNumber(String regionCode) {
355     PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
356     if (phoneMetadata == null) {
357       return "";
358     }
359     PhoneNumberDesc desc = phoneMetadata.getShortCode();
360     if (desc.hasExampleNumber()) {
361       return desc.getExampleNumber();
362     }
363     return "";
364   }
365 
366   /**
367    * Gets a valid short number for the specified cost category.
368    *
369    * @param regionCode the region for which an example short number is needed
370    * @param cost the cost category of number that is needed
371    * @return a valid short number for the specified region and cost category. Returns an empty
372    *     string when the metadata does not contain such information, or the cost is UNKNOWN_COST.
373    */
374   // @VisibleForTesting
getExampleShortNumberForCost(String regionCode, ShortNumberCost cost)375   String getExampleShortNumberForCost(String regionCode, ShortNumberCost cost) {
376     PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
377     if (phoneMetadata == null) {
378       return "";
379     }
380     PhoneNumberDesc desc = null;
381     switch (cost) {
382       case TOLL_FREE:
383         desc = phoneMetadata.getTollFree();
384         break;
385       case STANDARD_RATE:
386         desc = phoneMetadata.getStandardRate();
387         break;
388       case PREMIUM_RATE:
389         desc = phoneMetadata.getPremiumRate();
390         break;
391       default:
392         // UNKNOWN_COST numbers are computed by the process of elimination from the other cost
393         // categories.
394     }
395     if (desc != null && desc.hasExampleNumber()) {
396       return desc.getExampleNumber();
397     }
398     return "";
399   }
400 
401   /**
402    * Returns true if the given number, exactly as dialed, might be used to connect to an emergency
403    * service in the given region.
404    * <p>
405    * This method accepts a string, rather than a PhoneNumber, because it needs to distinguish
406    * cases such as "+1 911" and "911", where the former may not connect to an emergency service in
407    * all cases but the latter would. This method takes into account cases where the number might
408    * contain formatting, or might have additional digits appended (when it is okay to do that in
409    * the specified region).
410    *
411    * @param number the phone number to test
412    * @param regionCode the region where the phone number is being dialed
413    * @return whether the number might be used to connect to an emergency service in the given region
414    */
connectsToEmergencyNumber(String number, String regionCode)415   public boolean connectsToEmergencyNumber(String number, String regionCode) {
416     return matchesEmergencyNumberHelper(number, regionCode, true /* allows prefix match */);
417   }
418 
419   /**
420    * Returns true if the given number exactly matches an emergency service number in the given
421    * region.
422    * <p>
423    * This method takes into account cases where the number might contain formatting, but doesn't
424    * allow additional digits to be appended. Note that {@code isEmergencyNumber(number, region)}
425    * implies {@code connectsToEmergencyNumber(number, region)}.
426    *
427    * @param number the phone number to test
428    * @param regionCode the region where the phone number is being dialed
429    * @return whether the number exactly matches an emergency services number in the given region
430    */
isEmergencyNumber(String number, String regionCode)431   public boolean isEmergencyNumber(String number, String regionCode) {
432     return matchesEmergencyNumberHelper(number, regionCode, false /* doesn't allow prefix match */);
433   }
434 
matchesEmergencyNumberHelper(String number, String regionCode, boolean allowPrefixMatch)435   private boolean matchesEmergencyNumberHelper(String number, String regionCode,
436       boolean allowPrefixMatch) {
437     number = PhoneNumberUtil.extractPossibleNumber(number);
438     if (PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(number).lookingAt()) {
439       // Returns false if the number starts with a plus sign. We don't believe dialing the country
440       // code before emergency numbers (e.g. +1911) works, but later, if that proves to work, we can
441       // add additional logic here to handle it.
442       return false;
443     }
444     PhoneMetadata metadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
445     if (metadata == null || !metadata.hasEmergency()) {
446       return false;
447     }
448 
449     String normalizedNumber = PhoneNumberUtil.normalizeDigitsOnly(number);
450     PhoneNumberDesc emergencyDesc = metadata.getEmergency();
451     boolean allowPrefixMatchForRegion =
452         allowPrefixMatch && !REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT.contains(regionCode);
453     return matcherApi.matchesNationalNumber(normalizedNumber, emergencyDesc,
454         allowPrefixMatchForRegion);
455   }
456 
457   /**
458    * Given a valid short number, determines whether it is carrier-specific (however, nothing is
459    * implied about its validity). Carrier-specific numbers may connect to a different end-point, or
460    * not connect at all, depending on the user's carrier. If it is important that the number is
461    * valid, then its validity must first be checked using {@link #isValidShortNumber} or
462    * {@link #isValidShortNumberForRegion}.
463    *
464    * @param number the valid short number to check
465    * @return whether the short number is carrier-specific (assuming the input was a valid short
466    *     number).
467    */
isCarrierSpecific(PhoneNumber number)468   public boolean isCarrierSpecific(PhoneNumber number) {
469     List<String> regionCodes = getRegionCodesForCountryCode(number.getCountryCode());
470     String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes);
471     String nationalNumber = getNationalSignificantNumber(number);
472     PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode);
473     return (phoneMetadata != null)
474         && (matchesPossibleNumberAndNationalNumber(nationalNumber,
475                 phoneMetadata.getCarrierSpecific()));
476   }
477 
478   /**
479    * Given a valid short number, determines whether it is carrier-specific when dialed from the
480    * given region (however, nothing is implied about its validity). Carrier-specific numbers may
481    * connect to a different end-point, or not connect at all, depending on the user's carrier. If
482    * it is important that the number is valid, then its validity must first be checked using
483    * {@link #isValidShortNumber} or {@link #isValidShortNumberForRegion}. Returns false if the
484    * number doesn't match the region provided.
485    *
486    * @param number  the valid short number to check
487    * @param regionDialingFrom  the region from which the number is dialed
488    * @return  whether the short number is carrier-specific (assuming the input was a valid short
489    *     number)
490    */
isCarrierSpecificForRegion(PhoneNumber number, String regionDialingFrom)491   public boolean isCarrierSpecificForRegion(PhoneNumber number, String regionDialingFrom) {
492     if (!regionDialingFromMatchesNumber(number, regionDialingFrom)) {
493       return false;
494     }
495     String nationalNumber = getNationalSignificantNumber(number);
496     PhoneMetadata phoneMetadata =
497         MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom);
498     return (phoneMetadata != null)
499         && (matchesPossibleNumberAndNationalNumber(nationalNumber,
500                 phoneMetadata.getCarrierSpecific()));
501   }
502 
503   /**
504    * Gets the national significant number of the a phone number. Note a national significant number
505    * doesn't contain a national prefix or any formatting.
506    * <p>
507    * This is a temporary duplicate of the {@code getNationalSignificantNumber} method from
508    * {@code PhoneNumberUtil}. Ultimately a canonical static version should exist in a separate
509    * utility class (to prevent {@code ShortNumberInfo} needing to depend on PhoneNumberUtil).
510    *
511    * @param number  the phone number for which the national significant number is needed
512    * @return  the national significant number of the PhoneNumber object passed in
513    */
getNationalSignificantNumber(PhoneNumber number)514   private static String getNationalSignificantNumber(PhoneNumber number) {
515     // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
516     StringBuilder nationalNumber = new StringBuilder();
517     if (number.isItalianLeadingZero()) {
518       char[] zeros = new char[number.getNumberOfLeadingZeros()];
519       Arrays.fill(zeros, '0');
520       nationalNumber.append(new String(zeros));
521     }
522     nationalNumber.append(number.getNationalNumber());
523     return nationalNumber.toString();
524   }
525 
526   // TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth keeping
527   // this performance optimization.
matchesPossibleNumberAndNationalNumber(String number, PhoneNumberDesc numberDesc)528   private boolean matchesPossibleNumberAndNationalNumber(String number,
529       PhoneNumberDesc numberDesc) {
530     if (numberDesc.getPossibleLengthCount() > 0
531         && !numberDesc.getPossibleLengthList().contains(number.length())) {
532       return false;
533     }
534     return matcherApi.matchesNationalNumber(number, numberDesc, false);
535   }
536 }
537