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