• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @license
3 * Copyright (C) 2018 The Libphonenumber Authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18/**
19 * @fileoverview Utility for international phone numbers.
20 * Functionality includes formatting, parsing and validation.
21 * (based on the java implementation).
22 *
23 * NOTE: A lot of methods in this class require Region Code strings. These must
24 * be provided using CLDR two-letter region-code format. These should be in
25 * upper-case. The list of the codes can be found here:
26 * http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html
27 *
28 * @author James Wright
29 */
30
31goog.provide('i18n.phonenumbers.ShortNumberInfo');
32
33goog.require('goog.proto2.PbLiteSerializer');
34goog.require('i18n.phonenumbers.PhoneMetadata');
35goog.require('i18n.phonenumbers.PhoneNumber');
36goog.require('i18n.phonenumbers.PhoneNumberDesc');
37goog.require('i18n.phonenumbers.PhoneNumberUtil');
38goog.require('i18n.phonenumbers.metadata');
39goog.require('i18n.phonenumbers.shortnumbermetadata');
40
41
42
43/**
44 * @constructor
45 * @private
46 */
47i18n.phonenumbers.ShortNumberInfo = function() {
48  /**
49   * A mapping from region code to the short-number metadata for that region.
50   * @type {Object.<string, i18n.phonenumbers.PhoneMetadata>}
51   */
52  this.regionToMetadataMap = {};
53};
54goog.addSingletonGetter(i18n.phonenumbers.ShortNumberInfo);
55
56
57/**
58 * In these countries, if extra digits are added to an emergency number, it no
59 * longer connects to the emergency service.
60 * @const
61 * @type {!Array<string>}
62 * @private
63 */
64i18n.phonenumbers.ShortNumberInfo.
65    REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT_ = [
66      'BR',
67      'CL',
68      'NI'
69    ];
70
71
72/**
73 * @enum {number} Cost categories of short numbers.
74 */
75i18n.phonenumbers.ShortNumberInfo.ShortNumberCost = {
76  TOLL_FREE: 0,
77  STANDARD_RATE: 1,
78  PREMIUM_RATE: 2,
79  UNKNOWN_COST: 3
80};
81
82
83/**
84 * Returns a list with the region codes that match the specific country calling
85 * code. For non-geographical country calling codes, the region code 001 is
86 * returned. Also, in the case of no region code being found, an empty list
87 * is returned.
88 * @param {number} countryCallingCode
89 * @return {!Array<string>} The region codes that match the given country code.
90 * @private
91 */
92i18n.phonenumbers.ShortNumberInfo.prototype.getRegionCodesForCountryCode_ =
93    function(countryCallingCode) {
94  var regionCodes = i18n.phonenumbers.metadata
95          .countryCodeToRegionCodeMap[countryCallingCode];
96  return regionCodes ? regionCodes : [];
97};
98
99
100/**
101 * Helper method to check that the country calling code of the number matches
102 * the region it's being dialed from.
103 * @param {i18n.phonenumbers.PhoneNumber} number
104 * @param {?string} regionDialingFrom
105 * @return {boolean}
106 * @private
107 */
108i18n.phonenumbers.ShortNumberInfo.prototype.regionDialingFromMatchesNumber_ =
109    function(number, regionDialingFrom) {
110  var regionCodes = this.getRegionCodesForCountryCode_(
111      number.getCountryCodeOrDefault());
112  return regionDialingFrom != null && regionCodes.includes(regionDialingFrom);
113};
114
115
116/**
117 * Check whether a short number is a possible number when dialed from the given
118 * region. This provides a more lenient check than
119 * {@link #isValidShortNumberForRegion}.
120 *
121 * @param {i18n.phonenumbers.PhoneNumber} number the short number to check
122 * @param {string} regionDialingFrom the region from which the number is dialed
123 * @return {boolean} whether the number is a possible short number
124 */
125i18n.phonenumbers.ShortNumberInfo.prototype.isPossibleShortNumberForRegion =
126    function(number, regionDialingFrom) {
127  if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) {
128    return false;
129  }
130  var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom);
131  if (!phoneMetadata) {
132    return false;
133  }
134  var numberLength = this.getNationalSignificantNumber_(number).length;
135  return phoneMetadata.getGeneralDesc().possibleLengthArray().includes(
136      numberLength);
137};
138
139
140/**
141 * Check whether a short number is a possible number. If a country calling code
142 * is shared by multiple regions, this returns true if it's possible in any of
143 * them. This provides a more lenient check than {@link #isValidShortNumber}.
144 * See {@link #isPossibleShortNumberForRegion(PhoneNumber, String)} for details.
145 *
146 * @param {i18n.phonenumbers.PhoneNumber} number the short number to check
147 * @return {boolean} whether the number is a possible short number
148 */
149i18n.phonenumbers.ShortNumberInfo.prototype.isPossibleShortNumber =
150    function(number) {
151  var regionCodes = this.getRegionCodesForCountryCode_(
152      number.getCountryCodeOrDefault());
153  var shortNumberLength = this.getNationalSignificantNumber_(number).length;
154  for (var i = 0; i < regionCodes.length; i++) {
155    var region = regionCodes[i];
156    var phoneMetadata = this.getMetadataForRegion_(region);
157    if (!phoneMetadata) {
158      continue;
159    }
160    var possibleLengths = phoneMetadata.getGeneralDesc().possibleLengthArray();
161    if (possibleLengths.includes(shortNumberLength)) {
162      return true;
163    }
164  }
165  return false;
166};
167
168
169/**
170 * Tests whether a short number matches a valid pattern in a region. Note that
171 * this doesn't verify the number is actually in use, which is impossible to
172 * tell by just looking at the number itself.
173 *
174 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we
175 *     want to test the validity
176 * @param {?string} regionDialingFrom the region from which the number is dialed
177 * @return {boolean} whether the short number matches a valid pattern
178 */
179i18n.phonenumbers.ShortNumberInfo.prototype.isValidShortNumberForRegion =
180    function(number, regionDialingFrom) {
181  if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) {
182    return false;
183  }
184  var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom);
185  if (!phoneMetadata) {
186    return false;
187  }
188  var shortNumber = this.getNationalSignificantNumber_(number);
189  var generalDesc = phoneMetadata.getGeneralDesc();
190  if (!this.matchesPossibleNumberAndNationalNumber_(shortNumber, generalDesc)) {
191    return false;
192  }
193  var shortNumberDesc = phoneMetadata.getShortCode();
194  return this.matchesPossibleNumberAndNationalNumber_(shortNumber,
195                                                      shortNumberDesc);
196};
197
198
199/**
200 * Tests whether a short number matches a valid pattern. If a country calling
201 * code is shared by multiple regions, this returns true if it's valid in any of
202 * them. Note that this doesn't verify the number is actually in use, which is
203 * impossible to tell by just looking at the number itself. See
204 * {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details.
205 *
206 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we
207 *     want to test the validity
208 * @return {boolean} whether the short number matches a valid pattern
209 */
210i18n.phonenumbers.ShortNumberInfo.prototype.isValidShortNumber =
211    function(number) {
212  var regionCodes = this.getRegionCodesForCountryCode_(
213      number.getCountryCodeOrDefault());
214  var regionCode = this.getRegionCodeForShortNumberFromRegionList_(number,
215                                                                   regionCodes);
216  if (regionCodes.length > 1 && regionCode != null) {
217    // If a matching region had been found for the phone number from among two
218    // or more regions, then we have already implicitly verified its validity
219    // for that region.
220    return true;
221  }
222  return this.isValidShortNumberForRegion(number, regionCode);
223};
224
225
226/**
227 * Gets the expected cost category of a short number when dialed from a region
228 * (however, nothing is implied about its validity). If it is important that the
229 * number is valid, then its validity must first be checked using
230 * {@link #isValidShortNumberForRegion}. Note that emergency numbers are always
231 * considered toll-free. Example usage:
232 * <pre>{@code
233 * // The region for which the number was parsed and the region we subsequently
234 * // check against need not be the same. Here we parse the number in the US and
235 * // check it for Canada.
236 * PhoneNumber number = phoneUtil.parse("110", "US");
237 * ...
238 * String regionCode = "CA";
239 * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance();
240 * if (shortInfo.isValidShortNumberForRegion(shortNumber, regionCode)) {
241 *   ShortNumberCost cost = shortInfo.getExpectedCostForRegion(number,
242 *                                                             regionCode);
243 *   // Do something with the cost information here.
244 * }}</pre>
245 *
246 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we
247 *     want to know the expected cost category
248 * @param {string} regionDialingFrom the region from which the number is dialed
249 * @return {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} the expected cost
250 *     category for that region of the short number. Returns UNKNOWN_COST if the
251 *     number does not match a cost category. Note that an invalid number may
252 *     match any cost category.
253 * @package
254 */
255// @VisibleForTesting
256i18n.phonenumbers.ShortNumberInfo.prototype.getExpectedCostForRegion =
257    function(number, regionDialingFrom) {
258  var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost;
259  if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) {
260    return ShortNumberCost.UNKNOWN_COST;
261  }
262  var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom);
263  if (!phoneMetadata) {
264    return ShortNumberCost.UNKNOWN_COST;
265  }
266  var shortNumber = this.getNationalSignificantNumber_(number);
267
268  if (!phoneMetadata.getGeneralDesc().possibleLengthArray().includes(
269          shortNumber.length)) {
270    return ShortNumberCost.UNKNOWN_COST;
271  }
272  if (this.matchesPossibleNumberAndNationalNumber_(
273          shortNumber, phoneMetadata.getPremiumRate())) {
274    return ShortNumberCost.PREMIUM_RATE;
275  }
276  if (this.matchesPossibleNumberAndNationalNumber_(
277          shortNumber, phoneMetadata.getStandardRate())) {
278    return ShortNumberCost.STANDARD_RATE;
279  }
280  if (this.matchesPossibleNumberAndNationalNumber_(
281          shortNumber, phoneMetadata.getTollFree())) {
282    return ShortNumberCost.TOLL_FREE;
283  }
284  if (this.isEmergencyNumber(shortNumber, regionDialingFrom)) {
285    // Emergency numbers are implicitly toll-free
286    return ShortNumberCost.TOLL_FREE;
287  }
288  return ShortNumberCost.UNKNOWN_COST;
289};
290
291
292/**
293 * Gets the expected cost category of a short number (however, nothing is
294 * implied about its validity). If the country calling code is unique to a
295 * region, this method behaves exactly the same as
296 * {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the
297 * country calling code is shared by multiple regions, then it returns the
298 * highest cost in the sequence PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE,
299 * TOLL_FREE. The reason for the position of UNKNOWN_COST in this order is that
300 * if a number is UNKNOWN_COST in one region but STANDARD_RATE or TOLL_FREE in
301 * another, its expected cost cannot be estimated as one of the latter since it
302 * might be a PREMIUM_RATE number.
303 * <p>
304 * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada,
305 * the expected cost returned by this method will be STANDARD_RATE, since the
306 * NANPA countries share the same country calling code.
307 * <p>
308 * Note: If the region from which the number is dialed is known, it is highly
309 * preferable to call {@link #getExpectedCostForRegion(PhoneNumber, String)}
310 * instead.
311 *
312 * @param {i18n.phonenumbers.PhoneNumber} number the short number for which we
313 *     want to know the expected cost category
314 * @return {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} the highest
315 *     expected cost category of the short number in the region(s) with the
316 *     given country calling code
317 * @package
318 */
319// @VisibleForTesting
320i18n.phonenumbers.ShortNumberInfo.prototype.getExpectedCost = function(number) {
321  var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost;
322  var regionCodes = this.getRegionCodesForCountryCode_(
323      number.getCountryCodeOrDefault());
324  if (regionCodes.length === 0) {
325    return ShortNumberCost.UNKNOWN_COST;
326  }
327  if (regionCodes.length === 1) {
328    return this.getExpectedCostForRegion(number, regionCodes[0]);
329  }
330  var cost = ShortNumberCost.TOLL_FREE;
331  for (var i = 0; i < regionCodes.length; i++) {
332    var regionCode = regionCodes[i];
333    var costForRegion = this.getExpectedCostForRegion(number, regionCode);
334    switch (costForRegion) {
335      case ShortNumberCost.PREMIUM_RATE:
336        return ShortNumberCost.PREMIUM_RATE;
337      case ShortNumberCost.UNKNOWN_COST:
338        cost = ShortNumberCost.UNKNOWN_COST;
339        break;
340      case ShortNumberCost.STANDARD_RATE:
341        if (cost !== ShortNumberCost.UNKNOWN_COST) {
342          cost = ShortNumberCost.STANDARD_RATE;
343        }
344        break;
345      case ShortNumberCost.TOLL_FREE:
346        // Do nothing.
347        break;
348      default:
349        throw new Error('Unrecognized cost for region: ' + costForRegion);
350    }
351  }
352  return cost;
353};
354
355
356/**
357 * Helper method to get the region code for a given phone number, from a list
358 * of possible region codes. If the list contains more than one region, the
359 * first region for which the number is valid is returned.
360 * @param {!i18n.phonenumbers.PhoneNumber} number
361 * @param {Array<string>} regionCodes
362 * @return {?string}
363 * @private
364 */
365i18n.phonenumbers.ShortNumberInfo.prototype.getRegionCodeForShortNumberFromRegionList_ =
366    function(number, regionCodes) {
367  if (regionCodes.length === 0) {
368    return null;
369  } else if (regionCodes.length === 1) {
370    return regionCodes[0];
371  }
372  var nationalNumber = this.getNationalSignificantNumber_(number);
373  for (var i = 0; i < regionCodes.length; i++) {
374    var regionCode = regionCodes[i];
375    var phoneMetadata = this.getMetadataForRegion_(regionCode);
376    if (phoneMetadata && this.matchesPossibleNumberAndNationalNumber_(
377        nationalNumber, phoneMetadata.getShortCode())) {
378      return regionCode;
379    }
380  }
381  return null;
382};
383
384
385/**
386 * Convenience method to get a list of what regions the library has metadata for
387 * @return {!Array<string>} the list of region codes
388 * @package
389 */
390i18n.phonenumbers.ShortNumberInfo.prototype.getSupportedRegions = function() {
391  return Object.keys(i18n.phonenumbers.shortnumbermetadata.countryToMetadata)
392      .filter(function(regionCode) {
393        return isNaN(regionCode);
394      });
395};
396
397
398/**
399 * Gets a valid short number for the specified region.
400 *
401 * @param {?string} regionCode the region for which an example short number is
402 *     needed
403 * @return {string} a valid short number for the specified region. Returns an
404 *     empty string when the metadata does not contain such information.
405 * @package
406 */
407i18n.phonenumbers.ShortNumberInfo.prototype.getExampleShortNumber =
408    function(regionCode) {
409  var phoneMetadata = this.getMetadataForRegion_(regionCode);
410  if (!phoneMetadata) {
411    return '';
412  }
413  var desc = phoneMetadata.getShortCode();
414  if (desc.hasExampleNumber()) {
415    return desc.getExampleNumber() || '';
416  }
417  return '';
418};
419
420
421/**
422 * Gets a valid short number for the specified cost category.
423 *
424 * @param {string} regionCode the region for which an example short number is
425 *     needed
426 * @param {i18n.phonenumbers.ShortNumberInfo.ShortNumberCost} cost the cost
427 *     category of number that is needed
428 * @return {string} a valid short number for the specified region and cost
429 *     category. Returns an empty string when the metadata does not contain such
430 *     information, or the cost is UNKNOWN_COST.
431 */
432i18n.phonenumbers.ShortNumberInfo.prototype.getExampleShortNumberForCost =
433    function(regionCode, cost) {
434  var phoneMetadata = this.getMetadataForRegion_(regionCode);
435  if (!phoneMetadata) {
436    return '';
437  }
438  var ShortNumberCost = i18n.phonenumbers.ShortNumberInfo.ShortNumberCost;
439  var desc = null;
440  switch (cost) {
441    case ShortNumberCost.TOLL_FREE:
442      desc = phoneMetadata.getTollFree();
443      break;
444    case ShortNumberCost.STANDARD_RATE:
445      desc = phoneMetadata.getStandardRate();
446      break;
447    case ShortNumberCost.PREMIUM_RATE:
448      desc = phoneMetadata.getPremiumRate();
449      break;
450    default:
451      // UNKNOWN_COST numbers are computed by the process of elimination from
452      // the other cost categories.
453  }
454  if (desc && desc.hasExampleNumber()) {
455    return desc.getExampleNumber() || '';
456  }
457  return '';
458};
459
460
461/**
462 * Returns true if the given number, exactly as dialed, might be used to
463 * connect to an emergency service in the given region.
464 * <p>
465 * This method accepts a string, rather than a PhoneNumber, because it needs
466 * to distinguish cases such as "+1 911" and "911", where the former may not
467 * connect to an emergency service in all cases but the latter would. This
468 * method takes into account cases where the number might contain formatting,
469 * or might have additional digits appended (when it is okay to do that in
470 * the specified region).
471 *
472 * @param {string} number the phone number to test
473 * @param {string} regionCode the region where the phone number is being
474 *     dialed
475 * @return {boolean} whether the number might be used to connect to an
476 *     emergency service in the given region
477 */
478i18n.phonenumbers.ShortNumberInfo.prototype.connectsToEmergencyNumber =
479    function(number, regionCode) {
480  return this.matchesEmergencyNumberHelper_(number, regionCode,
481      true /* allows prefix match */);
482};
483
484
485/**
486 * Returns true if the given number exactly matches an emergency service
487 * number in the given region.
488 * <p>
489 * This method takes into account cases where the number might contain
490 * formatting, but doesn't allow additional digits to be appended. Note that
491 * {@code isEmergencyNumber(number, region)} implies
492 * {@code connectsToEmergencyNumber(number, region)}.
493 *
494 * @param {string} number the phone number to test
495 * @param {string} regionCode the region where the phone number is being
496 *     dialed
497 * @return {boolean} whether the number exactly matches an emergency services
498 *     number in the given region.
499 */
500i18n.phonenumbers.ShortNumberInfo.prototype.isEmergencyNumber =
501    function(number, regionCode) {
502  return this.matchesEmergencyNumberHelper_(number, regionCode,
503      false /* doesn't allow prefix match */);
504};
505
506
507/**
508 * @param {?string} regionCode The region code to get metadata for
509 * @return {?i18n.phonenumbers.PhoneMetadata} The region code's metadata, or
510 *     null if it is not available or the region code is invalid.
511 * @private
512 */
513i18n.phonenumbers.ShortNumberInfo.prototype.getMetadataForRegion_ =
514    function(regionCode) {
515  if (!regionCode) {
516    return null;
517  }
518  regionCode = regionCode.toUpperCase();
519  var metadata = this.regionToMetadataMap[regionCode];
520  if (metadata == null) {
521    /** @type {goog.proto2.PbLiteSerializer} */
522    var serializer = new goog.proto2.PbLiteSerializer();
523    var metadataSerialized =
524        i18n.phonenumbers.shortnumbermetadata.countryToMetadata[regionCode];
525    if (metadataSerialized == null) {
526      return null;
527    }
528    metadata = /** @type {i18n.phonenumbers.PhoneMetadata} */ (
529        serializer.deserialize(i18n.phonenumbers.PhoneMetadata.getDescriptor(),
530        metadataSerialized));
531    this.regionToMetadataMap[regionCode] = metadata;
532  }
533  return metadata;
534};
535
536
537/**
538 * @param {string} number the number to match against
539 * @param {string} regionCode the region code to check against
540 * @param {boolean} allowPrefixMatch whether to allow prefix matching
541 * @return {boolean} True iff the number matches an emergency number for that
542 *     particular region.
543 * @private
544 */
545i18n.phonenumbers.ShortNumberInfo.prototype.matchesEmergencyNumberHelper_ =
546    function(number, regionCode, allowPrefixMatch) {
547  var possibleNumber = i18n.phonenumbers.PhoneNumberUtil
548      .extractPossibleNumber(number);
549  if (i18n.phonenumbers.PhoneNumberUtil.LEADING_PLUS_CHARS_PATTERN
550      .test(possibleNumber)) {
551    return false;
552  }
553  var metadata = this.getMetadataForRegion_(regionCode);
554  if (metadata == null || !metadata.hasEmergency()) {
555    return false;
556  }
557
558  var normalizedNumber = i18n.phonenumbers.PhoneNumberUtil
559      .normalizeDigitsOnly(possibleNumber);
560  var allowPrefixMatchForRegion = allowPrefixMatch &&
561      !i18n.phonenumbers.ShortNumberInfo
562           .REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT_.includes(regionCode);
563  var emergencyNumberPattern = metadata.getEmergency()
564      .getNationalNumberPatternOrDefault();
565  var result = i18n.phonenumbers.PhoneNumberUtil.matchesEntirely(
566      emergencyNumberPattern, normalizedNumber);
567  return result ||
568         (allowPrefixMatchForRegion &&
569          i18n.phonenumbers.PhoneNumberUtil
570              .matchesPrefix(emergencyNumberPattern, normalizedNumber));
571};
572
573
574/**
575 * Given a valid short number, determines whether it is carrier-specific
576 * (however, nothing is implied about its validity). Carrier-specific numbers
577 * may connect to a different end-point, or not connect at all, depending on
578 * the user's carrier. If it is important that the number is valid, then its
579 * validity must first be checked using {@link #isValidShortNumber} or
580 * {@link #isValidShortNumberForRegion}.
581 *
582 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to
583 *     check
584 * @return {boolean} whether the short number is carrier-specific, assuming the
585 *     input was a valid short number
586 */
587i18n.phonenumbers.ShortNumberInfo.prototype.isCarrierSpecific =
588    function(number) {
589  var regionCodes = this.getRegionCodesForCountryCode_(
590      number.getCountryCodeOrDefault());
591  var regionCode = this.getRegionCodeForShortNumberFromRegionList_(number,
592                                                                   regionCodes);
593  var nationalNumber = this.getNationalSignificantNumber_(number);
594  var phoneMetadata = this.getMetadataForRegion_(regionCode);
595  return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_(
596      nationalNumber, phoneMetadata.getCarrierSpecific());
597};
598
599
600/**
601 * Given a valid short number, determines whether it is carrier-specific when
602 * dialed from the given region (however, nothing is implied about its
603 * validity). Carrier-specific numbers may connect to a different end-point, or
604 * not connect at all, depending on the user's carrier. If it is important that
605 * the number is valid, then its validity must first be checked using
606 * {@link #isValidShortNumber} or {@link #isValidShortNumberForRegion}. Returns
607 * false if the number doesn't match the region provided.
608 *
609 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to
610 *     check
611 * @param {string} regionDialingFrom the region from which the number is dialed
612 * @return {boolean} whether the short number is carrier-specific in the
613 *     provided region, assuming the input was a valid short number
614 */
615i18n.phonenumbers.ShortNumberInfo.prototype.isCarrierSpecificForRegion =
616    function(number, regionDialingFrom) {
617  if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) {
618    return false;
619  }
620  var nationalNumber = this.getNationalSignificantNumber_(number);
621  var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom);
622  return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_(
623      nationalNumber, phoneMetadata.getCarrierSpecific());
624};
625
626
627/**
628 * Given a valid short number, determines whether it is an SMS service
629 * (however, nothing is implied about its validity). An SMS service is where the
630 * primary or only intended usage is to receive and/or send text messages
631 * (SMSs). This includes MMS as MMS numbers downgrade to SMS if the other party
632 * isn't MMS-capable. If it is important that the number is valid, then its
633 * validity must first be checked using {@link #isValidShortNumber} or {@link
634 * #isValidShortNumberForRegion}. Returns false if the number doesn't match the
635 * region provided.
636 *
637 * @param {i18n.phonenumbers.PhoneNumber} number the valid short number to
638 *     check
639 * @param {string} regionDialingFrom the region from which the number is dialed
640 * @return {boolean} whether the short number is an SMS service in the provided
641 *     region, assuming the input was a valid short number
642 */
643i18n.phonenumbers.ShortNumberInfo.prototype.isSmsServiceForRegion =
644    function(number, regionDialingFrom) {
645  if (!this.regionDialingFromMatchesNumber_(number, regionDialingFrom)) {
646    return false;
647  }
648  var phoneMetadata = this.getMetadataForRegion_(regionDialingFrom);
649  var nationalNumber = this.getNationalSignificantNumber_(number);
650  return !!phoneMetadata && this.matchesPossibleNumberAndNationalNumber_(
651      nationalNumber, phoneMetadata.getSmsServices());
652};
653
654
655/**
656 * Gets the national significant number of a phone number. Note a national
657 * significant number doesn't contain a national prefix or any formatting.
658 * <p>
659 * This is a temporary duplicate of the {@code getNationalSignificantNumber}
660 * method from {@code PhoneNumberUtil}. Ultimately a canonical static version
661 * should exist in a separate utility class (to prevent {@code ShortNumberInfo}
662 * needing to depend on PhoneNumberUtil).
663 *
664 * @param {i18n.phonenumbers.PhoneNumber} number the phone number for which the
665 *     national significant number is needed.
666 * @return {string} the national significant number of the PhoneNumber object
667 *     passed in.
668 * @private
669 */
670i18n.phonenumbers.ShortNumberInfo.prototype.getNationalSignificantNumber_ =
671    function(number) {
672  if (!number.hasNationalNumber()) {
673    return '';
674  }
675  /** @type {string} */
676  var nationalNumber = '' + number.getNationalNumber();
677  // If leading zero(s) have been set, we prefix this now. Note that a single
678  // leading zero is not the same as a national prefix; leading zeros should be
679  // dialled no matter whether you are dialling from within or outside the
680  // country, national prefixes are added when formatting nationally if
681  // applicable.
682  if (number.hasItalianLeadingZero() && number.getItalianLeadingZero() &&
683      number.getNumberOfLeadingZerosOrDefault() > 0) {
684    return Array(number.getNumberOfLeadingZerosOrDefault() + 1).join('0') +
685        nationalNumber;
686  }
687  return nationalNumber;
688};
689
690
691/**
692 * Helper method to add in a performance optimization.
693 * TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth
694 * keeping this performance optimization.
695 * @param {string} number
696 * @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
697 * @return {boolean}
698 * @private
699 */
700i18n.phonenumbers.ShortNumberInfo.prototype
701    .matchesPossibleNumberAndNationalNumber_ = function(number, numberDesc) {
702  if (numberDesc.possibleLengthArray().length > 0 &&
703      !numberDesc.possibleLengthArray().includes(number.length)) {
704    return false;
705  }
706  return i18n.phonenumbers.PhoneNumberUtil.matchesEntirely(
707      numberDesc.getNationalNumberPatternOrDefault(), number.toString());
708};
709