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