Commits

Anonymous committed 7754cfe

JAVA: libphonenumber 4.5
Review URL: http://codereview.appspot.com/5532089

Comments (0)

Files changed (57)

java/geocoder/src/com/google/i18n/phonenumbers/geocoding/data/1667_en

Binary file added.

java/geocoder/src/com/google/i18n/phonenumbers/geocoding/data/1984_en

Binary file added.

java/geocoder/src/com/google/i18n/phonenumbers/geocoding/data/54_en

Binary file modified.

java/geocoder/src/com/google/i18n/phonenumbers/geocoding/data/config

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java

     nationalNumber.setLength(0);
     nationalNumber.append(numberWithoutCountryCallingCode);
     String newRegionCode = phoneUtil.getRegionCodeForCountryCode(countryCode);
-    if (!newRegionCode.equals(defaultCountry)) {
+    if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(newRegionCode)) {
+      currentMetaData = phoneUtil.getMetadataForNonGeographicalRegion(countryCode);
+    } else if (!newRegionCode.equals(defaultCountry)) {
       currentMetaData = getMetadataForRegion(newRegionCode);
     }
     String countryCodeString = Integer.toString(countryCode);

java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java

   // countries sharing a calling code, such as the NANPA countries, the one
   // indicated with "isMainCountryForCode" in the metadata should be first.
   static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {
-    // The capacity is set to 273 as there are 205 different country codes,
+    // The capacity is set to 280 as there are 210 different country codes,
     // and this offers a load factor of roughly 0.75.
     Map<Integer, List<String>> countryCodeToRegionCodeMap =
-        new HashMap<Integer, List<String>>(273);
+        new HashMap<Integer, List<String>>(280);
 
     ArrayList<String> listWithRegionCode;
 
     countryCodeToRegionCodeMap.put(692, listWithRegionCode);
 
     listWithRegionCode = new ArrayList<String>(1);
+    listWithRegionCode.add("001");
+    countryCodeToRegionCodeMap.put(800, listWithRegionCode);
+
+    listWithRegionCode = new ArrayList<String>(1);
+    listWithRegionCode.add("001");
+    countryCodeToRegionCodeMap.put(808, listWithRegionCode);
+
+    listWithRegionCode = new ArrayList<String>(1);
     listWithRegionCode.add("KP");
     countryCodeToRegionCodeMap.put(850, listWithRegionCode);
 
     countryCodeToRegionCodeMap.put(880, listWithRegionCode);
 
     listWithRegionCode = new ArrayList<String>(1);
+    listWithRegionCode.add("001");
+    countryCodeToRegionCodeMap.put(883, listWithRegionCode);
+
+    listWithRegionCode = new ArrayList<String>(1);
     listWithRegionCode.add("TW");
     countryCodeToRegionCodeMap.put(886, listWithRegionCode);
 
     listWithRegionCode = new ArrayList<String>(1);
+    listWithRegionCode.add("001");
+    countryCodeToRegionCodeMap.put(888, listWithRegionCode);
+
+    listWithRegionCode = new ArrayList<String>(1);
     listWithRegionCode.add("MV");
     countryCodeToRegionCodeMap.put(960, listWithRegionCode);
 
     countryCodeToRegionCodeMap.put(977, listWithRegionCode);
 
     listWithRegionCode = new ArrayList<String>(1);
+    listWithRegionCode.add("001");
+    countryCodeToRegionCodeMap.put(979, listWithRegionCode);
+
+    listWithRegionCode = new ArrayList<String>(1);
     listWithRegionCode.add("TJ");
     countryCodeToRegionCodeMap.put(992, listWithRegionCode);
 

java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java

   private Map<Integer, List<String>> countryCallingCodeToRegionCodeMap = null;
 
   // The set of regions the library supports.
-  // There are roughly 220 of them and we set the initial capacity of the HashSet to 300 to offer a
+  // There are roughly 240 of them and we set the initial capacity of the HashSet to 320 to offer a
   // load factor of roughly 0.75.
-  private final Set<String> supportedRegions = new HashSet<String>(300);
+  private final Set<String> supportedRegions = new HashSet<String>(320);
 
   // Region-code for the unknown region.
   private static final String UNKNOWN_REGION = "ZZ";
   private final Map<String, PhoneMetadata> regionToMetadataMap =
       Collections.synchronizedMap(new HashMap<String, PhoneMetadata>());
 
+  // A mapping from a country calling code for a non-geographical entity to the PhoneMetadata for
+  // that country calling code. Examples of the country calling codes include 800 (International
+  // Toll Free Service) and 808 (International Shared Cost Service).
+  private final Map<Integer, PhoneMetadata> countryCodeToNonGeographicalMetadataMap =
+      Collections.synchronizedMap(new HashMap<Integer, PhoneMetadata>());
+
   // A cache for frequently used region-specific regular expressions.
   // As most people use phone numbers primarily from one to two countries, and there are roughly 60
   // regular expressions needed, the initial capacity of 100 offers a rough load factor of 0.75.
   private RegexCache regexCache = new RegexCache(100);
 
+  public static final String REGION_CODE_FOR_NON_GEO_ENTITY = "001";
+
   /**
    * INTERNATIONAL and NATIONAL formats are consistent with the definition in ITU-T Recommendation
    * E123. For example, the number of the Google Switzerland office will be written as
     for (List<String> regionCodes : countryCallingCodeToRegionCodeMap.values()) {
       supportedRegions.addAll(regionCodes);
     }
+    supportedRegions.remove(REGION_CODE_FOR_NON_GEO_ENTITY);
     nanpaRegions.addAll(countryCallingCodeToRegionCodeMap.get(NANPA_COUNTRY_CODE));
   }
 
-  private void loadMetadataForRegionFromFile(String filePrefix, String regionCode) {
-    InputStream source =
-        PhoneNumberUtil.class.getResourceAsStream(filePrefix + "_" + regionCode);
+  private void loadMetadataFromFile(String filePrefix, String regionCode, int countryCallingCode) {
+    boolean isNonGeoRegion = REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode);
+    InputStream source = isNonGeoRegion
+        ? PhoneNumberUtil.class.getResourceAsStream(filePrefix + "_" + countryCallingCode)
+        : PhoneNumberUtil.class.getResourceAsStream(filePrefix + "_" + regionCode);
     ObjectInputStream in = null;
     try {
       in = new ObjectInputStream(source);
       PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection();
       metadataCollection.readExternal(in);
       for (PhoneMetadata metadata : metadataCollection.getMetadataList()) {
-        regionToMetadataMap.put(regionCode, metadata);
+        if (isNonGeoRegion) {
+          countryCodeToNonGeographicalMetadataMap.put(countryCallingCode, metadata);
+        } else {
+          regionToMetadataMap.put(regionCode, metadata);
+        }
       }
     } catch (IOException e) {
       LOGGER.log(Level.WARNING, e.toString());
    *    therefore, it doesn't guarantee the stability of the result it produces.
    *  <li> subscriber numbers may not be diallable from all devices (notably mobile devices, which
    *    typically requires the full national_number to be dialled in most regions).
-   *  <li> most non-geographical numbers have no area codes.
+   *  <li> most non-geographical numbers have no area codes, including numbers from non-geographical
+   *    entities
    *  <li> some geographical numbers have no area codes.
    * </ul>
    * @param number  the PhoneNumber object for which clients want to know the length of the area
   }
 
   /**
-   * Helper function to check region code is not unknown or null and log an error message. The
-   * {@code countryCallingCode} and {@code number} supplied is used only for the resultant log
-   * message.
+   * Helper function to check the country calling code is valid.
    */
-  private boolean hasValidRegionCode(String regionCode,
-                                     int countryCallingCode, String number) {
-    if (!isValidRegionCode(regionCode)) {
-      LOGGER.log(Level.WARNING,
-                 "Number " + number + " has invalid or missing country calling code ("
-                 + countryCallingCode + ")");
-      return false;
-    }
-    return true;
+  private boolean hasValidCountryCallingCode(int countryCallingCode) {
+    return countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode);
   }
 
   /**
     // share a country calling code is contained by only one region for performance reasons. For
     // example, for NANPA regions it will be contained in the metadata for US.
     String regionCode = getRegionCodeForCountryCode(countryCallingCode);
-    if (!isValidRegionCode(regionCode)) {
+   if (!hasValidCountryCallingCode(countryCallingCode)) {
       formattedNumber.append(nationalSignificantNumber);
       return;
     }
 
-    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    PhoneMetadata metadata =
+        getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
     formattedNumber.append(formatNationalNumber(nationalSignificantNumber, metadata, numberFormat));
     maybeGetFormattedExtension(number, metadata, numberFormat, formattedNumber);
     formatNumberByFormat(countryCallingCode, numberFormat, formattedNumber);
     // share a country calling code is contained by only one region for performance reasons. For
     // example, for NANPA regions it will be contained in the metadata for US.
     String regionCode = getRegionCodeForCountryCode(countryCallingCode);
-    if (!hasValidRegionCode(regionCode, countryCallingCode, nationalSignificantNumber)) {
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
       return nationalSignificantNumber;
     }
     List<NumberFormat> userDefinedFormatsCopy =
         new ArrayList<NumberFormat>(userDefinedFormats.size());
-    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    PhoneMetadata metadata =
+        getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
     for (NumberFormat numFormat : userDefinedFormats) {
       String nationalPrefixFormattingRule = numFormat.getNationalPrefixFormattingRule();
       if (nationalPrefixFormattingRule.length() > 0) {
     // share a country calling code is contained by only one region for performance reasons. For
     // example, for NANPA regions it will be contained in the metadata for US.
     String regionCode = getRegionCodeForCountryCode(countryCallingCode);
-    if (!hasValidRegionCode(regionCode, countryCallingCode, nationalSignificantNumber)) {
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
       return nationalSignificantNumber;
     }
 
     StringBuilder formattedNumber = new StringBuilder(20);
-    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
     formattedNumber.append(formatNationalNumber(nationalSignificantNumber,
                                                 metadata,
                                                 PhoneNumberFormat.NATIONAL,
     return formattedNumber.toString();
   }
 
+  private PhoneMetadata getMetadataForRegionOrCallingCode(
+      int countryCallingCode, String regionCode) {
+    return REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode)
+        ? getMetadataForNonGeographicalRegion(countryCallingCode)
+        : getMetadataForRegion(regionCode);
+  }
+
   /**
    * Formats a phone number in national format for dialing using the carrier as specified in the
    * preferredDomesticCarrierCode field of the PhoneNumber object passed in. If that is missing,
    */
   public String formatNumberForMobileDialing(PhoneNumber number, String regionCallingFrom,
                                              boolean withFormatting) {
-    String regionCode = getRegionCodeForCountryCode(number.getCountryCode());
-    if (!isValidRegionCode(regionCode)) {
+    int countryCallingCode = number.getCountryCode();
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
       return number.hasRawInput() ? number.getRawInput() : "";
     }
 
     // Clear the extension, as that part cannot normally be dialed together with the main number.
     PhoneNumber numberNoExt = new PhoneNumber().mergeFrom(number).clearExtension();
     PhoneNumberType numberType = getNumberType(numberNoExt);
+    String regionCode = getRegionCodeForCountryCode(countryCallingCode);
     if (regionCode.equals("CO") && regionCallingFrom.equals("CO")) {
       if (numberType == PhoneNumberType.FIXED_LINE) {
         formattedNumber =
       return format(number, PhoneNumberFormat.INTERNATIONAL);
     }
     int countryCallingCode = number.getCountryCode();
-    String regionCode = getRegionCodeForCountryCode(countryCallingCode);
     String nationalSignificantNumber = getNationalSignificantNumber(number);
-    if (!hasValidRegionCode(regionCode, countryCallingCode, nationalSignificantNumber)) {
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
       return nationalSignificantNumber;
     }
     if (countryCallingCode == NANPA_COUNTRY_CODE) {
           metadataForRegionCallingFrom.getPreferredInternationalPrefix();
     }
 
-    PhoneMetadata metadataForRegion = getMetadataForRegion(regionCode);
+    String regionCode = getRegionCodeForCountryCode(countryCallingCode);
+    PhoneMetadata metadataForRegion =
+        getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
     String formattedNationalNumber =
         formatNationalNumber(nationalSignificantNumber,
                              metadataForRegion, PhoneNumberFormat.INTERNATIONAL);
    * country, or we don't have a formatting pattern for the number, the method returns the raw input
    * when it is available.
    *
+   * Note this method guarantees no digit will be inserted, removed or modified as a result of
+   * formatting.
    * @param number  the phone number that needs to be formatted in its original number format
    * @param regionCallingFrom  the region whose IDD needs to be prefixed if the original number
    *     has one
     if (!number.hasCountryCodeSource()) {
       return format(number, PhoneNumberFormat.NATIONAL);
     }
+    String formattedNumber;
     switch (number.getCountryCodeSource()) {
       case FROM_NUMBER_WITH_PLUS_SIGN:
-        return format(number, PhoneNumberFormat.INTERNATIONAL);
+        formattedNumber = format(number, PhoneNumberFormat.INTERNATIONAL);
+        break;
       case FROM_NUMBER_WITH_IDD:
-        return formatOutOfCountryCallingNumber(number, regionCallingFrom);
+        formattedNumber = formatOutOfCountryCallingNumber(number, regionCallingFrom);
+        break;
       case FROM_NUMBER_WITHOUT_PLUS_SIGN:
-        return format(number, PhoneNumberFormat.INTERNATIONAL).substring(1);
+        formattedNumber = format(number, PhoneNumberFormat.INTERNATIONAL).substring(1);
+        break;
       case FROM_DEFAULT_COUNTRY:
+        // Fall-through to default case.
       default:
-        return format(number, PhoneNumberFormat.NATIONAL);
+        String regionCode = getRegionCodeForCountryCode(number.getCountryCode());
+        // We strip non-digits from the NDD here, and from the raw input later, so that we can
+        // compare them easily.
+        String nationalPrefix = getNddPrefixForRegion(regionCode, true /* strip non-digits */);
+        String nationalFormat = format(number, PhoneNumberFormat.NATIONAL);
+        if (nationalPrefix == null || nationalPrefix.length() == 0) {
+          // If the region doesn't have a national prefix at all, we can safely return the national
+          // format without worrying about a national prefix being added.
+          formattedNumber = nationalFormat;
+          break;
+        }
+        // Otherwise, we check if the original number was entered with a national prefix.
+        if (rawInputContainsNationalPrefix(
+            number.getRawInput(), nationalPrefix, regionCode)) {
+          // If so, we can safely return the national format.
+          formattedNumber = nationalFormat;
+          break;
+        }
+        PhoneMetadata metadata = getMetadataForRegion(regionCode);
+        String nationalNumber = getNationalSignificantNumber(number);
+        NumberFormat formatRule =
+            chooseFormattingPatternForNumber(metadata.numberFormats(), nationalNumber);
+        // When the format we apply to this number doesn't contain national prefix, we can just
+        // return the national format.
+        // TODO: Refactor the code below with the code in isNationalPrefixPresentIfRequired.
+        String candidateNationalPrefixRule = formatRule.getNationalPrefixFormattingRule();
+        // We assume that the first-group symbol will never be _before_ the national prefix.
+        int indexOfFirstGroup = candidateNationalPrefixRule.indexOf("$1");
+        if (indexOfFirstGroup <= 0) {
+          formattedNumber = nationalFormat;
+          break;
+        }
+        candidateNationalPrefixRule =
+            candidateNationalPrefixRule.substring(0, indexOfFirstGroup);
+        candidateNationalPrefixRule = normalizeDigitsOnly(candidateNationalPrefixRule);
+        if (candidateNationalPrefixRule.length() == 0) {
+          // National prefix not used when formatting this number.
+          formattedNumber = nationalFormat;
+          break;
+        }
+        // Otherwise, we need to remove the national prefix from our output.
+        formatRule.clearNationalPrefixFormattingRule();
+        List<NumberFormat> numberFormats = new ArrayList<NumberFormat>(1);
+        numberFormats.add(formatRule);
+        formattedNumber = formatByPattern(number, PhoneNumberFormat.NATIONAL, numberFormats);
+        break;
     }
+    String rawInput = number.getRawInput();
+    // If no digit is inserted/removed/modified as a result of our formatting, we return the
+    // formatted phone number; otherwise we return the raw input the user entered.
+    return (formattedNumber != null &&
+            normalizeDigitsOnly(formattedNumber).equals(normalizeDigitsOnly(rawInput)))
+        ? formattedNumber
+        : rawInput;
+  }
+
+  // Check if rawInput, which is assumed to be in the national format, has a national prefix. The
+  // national prefix is assumed to be in digits-only form.
+  private boolean rawInputContainsNationalPrefix(String rawInput, String nationalPrefix,
+      String regionCode) {
+    String normalizedNationalNumber = normalizeDigitsOnly(rawInput);
+    if (normalizedNationalNumber.startsWith(nationalPrefix)) {
+      try {
+        // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix
+        // when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we
+        // check the validity of the number if the assumed national prefix is removed (777123 won't
+        // be valid in Japan).
+        return isValidNumber(
+            parse(normalizedNationalNumber.substring(nationalPrefix.length()), regionCode));
+      } catch (NumberParseException e) {
+        return false;
+      }
+    }
+    return false;
   }
 
   /**
   }
 
   private boolean hasFormattingPatternForNumber(PhoneNumber number) {
-    String phoneNumberRegion = getRegionCodeForCountryCode(number.getCountryCode());
-    PhoneMetadata metadata = getMetadataForRegion(phoneNumberRegion);
+    int countryCallingCode = number.getCountryCode();
+    String phoneNumberRegion = getRegionCodeForCountryCode(countryCallingCode);
+    PhoneMetadata metadata =
+        getMetadataForRegionOrCallingCode(countryCallingCode, phoneNumberRegion);
     if (metadata == null) {
       return false;
     }
       return formatOutOfCountryCallingNumber(number, regionCallingFrom);
     }
     int countryCode = number.getCountryCode();
-    String regionCode = getRegionCodeForCountryCode(countryCode);
-    if (!hasValidRegionCode(regionCode, countryCode, rawInput)) {
+    if (!hasValidCountryCallingCode(countryCode)) {
       return rawInput;
     }
     // Strip any prefix such as country calling code, IDD, that was present. We do this by comparing
         rawInput = rawInput.substring(firstNationalNumberDigit);
       }
     }
-    PhoneMetadata metadata = getMetadataForRegion(regionCallingFrom);
+    PhoneMetadata metadataForRegionCallingFrom = getMetadataForRegion(regionCallingFrom);
     if (countryCode == NANPA_COUNTRY_CODE) {
       if (isNANPACountry(regionCallingFrom)) {
         return countryCode + " " + rawInput;
     } else if (countryCode == getCountryCodeForRegion(regionCallingFrom)) {
       // Here we copy the formatting rules so we can modify the pattern we expect to match against.
       List<NumberFormat> availableFormats =
-          new ArrayList<NumberFormat>(metadata.numberFormatSize());
-      for (NumberFormat format : metadata.numberFormats()) {
+          new ArrayList<NumberFormat>(metadataForRegionCallingFrom.numberFormatSize());
+      for (NumberFormat format : metadataForRegionCallingFrom.numberFormats()) {
         NumberFormat newFormat = new NumberFormat();
         newFormat.mergeFrom(format);
         // The first group is the first group of digits that the user determined.
       // anything, but that is not the case in the metadata to date.
       return formatAccordingToFormats(rawInput, availableFormats, PhoneNumberFormat.NATIONAL);
     }
-    String internationalPrefix = metadata.getInternationalPrefix();
-    // For countries that have multiple international prefixes, the international format of the
-    // number is returned, unless there is a preferred international prefix.
-    String internationalPrefixForFormatting =
-        UNIQUE_INTERNATIONAL_PREFIX.matcher(internationalPrefix).matches()
-        ? internationalPrefix
-        : metadata.getPreferredInternationalPrefix();
+    String internationalPrefixForFormatting = "";
+    // If an unsupported region-calling-from is entered, or a country with multiple international
+    // prefixes, the international format of the number is returned, unless there is a preferred
+    // international prefix.
+    if (metadataForRegionCallingFrom != null) {
+      String internationalPrefix = metadataForRegionCallingFrom.getInternationalPrefix();
+      internationalPrefixForFormatting =
+          UNIQUE_INTERNATIONAL_PREFIX.matcher(internationalPrefix).matches()
+          ? internationalPrefix
+          : metadataForRegionCallingFrom.getPreferredInternationalPrefix();
+    }
     StringBuilder formattedNumber = new StringBuilder(rawInput);
-    maybeGetFormattedExtension(number, getMetadataForRegion(regionCode),
+    String regionCode = getRegionCodeForCountryCode(countryCode);
+    PhoneMetadata metadataForRegion = getMetadataForRegionOrCallingCode(countryCode, regionCode);
+    maybeGetFormattedExtension(number, metadataForRegion,
                                PhoneNumberFormat.INTERNATIONAL, formattedNumber);
     if (internationalPrefixForFormatting.length() > 0) {
       formattedNumber.insert(0, " ").insert(0, countryCode).insert(0, " ")
           .insert(0, internationalPrefixForFormatting);
     } else {
+      // Invalid region entered as country-calling-from (so no metadata was found for it) or the
+      // region chosen has multiple international dialling prefixes.
       formatNumberByFormat(countryCode,
                            PhoneNumberFormat.INTERNATIONAL,
                            formattedNumber);
    *
    * @param regionCode  the region for which an example number is needed
    * @return  a valid fixed-line number for the specified region. Returns null when the metadata
-   *    does not contain such information.
+   *    does not contain such information, or the region 001 is passed in. For 001 (representing
+   *    non-geographical numbers), call {@link #getExampleNumberForNonGeoEntity} instead.
    */
   public PhoneNumber getExampleNumber(String regionCode) {
     return getExampleNumberForType(regionCode, PhoneNumberType.FIXED_LINE);
    * @param regionCode  the region for which an example number is needed
    * @param type  the type of number that is needed
    * @return  a valid number for the specified region and type. Returns null when the metadata
-   *     does not contain such information or if an invalid region was entered.
+   *     does not contain such information or if an invalid region or region 001 was entered.
+   *     For 001 (representing non-geographical numbers), call
+   *     {@link #getExampleNumberForNonGeoEntity} instead.
    */
   public PhoneNumber getExampleNumberForType(String regionCode, PhoneNumberType type) {
     // Check the region code is valid.
   }
 
   /**
+   * Gets a valid number for the specified country calling code for a non-geographical entity.
+   *
+   * @param countryCallingCode  the country calling code for a non-geographical entity
+   * @return  a valid number for the non-geographical entity. Returns null when the metadata
+   *    does not contain such information, or the country calling code passed in does not belong
+   *    to a non-geographical entity.
+   */
+  public PhoneNumber getExampleNumberForNonGeoEntity(int countryCallingCode) {
+    PhoneMetadata metadata = getMetadataForNonGeographicalRegion(countryCallingCode);
+    if (metadata != null) {
+      PhoneNumberDesc desc = metadata.getGeneralDesc();
+      try {
+        if (desc.hasExampleNumber()) {
+          return parse("+" + countryCallingCode + desc.getExampleNumber(), "ZZ");
+        }
+      } catch (NumberParseException e) {
+        LOGGER.log(Level.SEVERE, e.toString());
+      }
+    }
+    return null;
+  }
+
+  /**
    * Appends the formatted extension of a phone number to formattedNumber, if the phone number had
    * an extension specified.
    */
    */
   public PhoneNumberType getNumberType(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
-    if (!isValidRegionCode(regionCode)) {
+    if (!isValidRegionCode(regionCode) && !REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode)) {
       return PhoneNumberType.UNKNOWN;
     }
     String nationalSignificantNumber = getNationalSignificantNumber(number);
-    return getNumberTypeHelper(nationalSignificantNumber, getMetadataForRegion(regionCode));
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(number.getCountryCode(), regionCode);
+    return getNumberTypeHelper(nationalSignificantNumber, metadata);
   }
 
   private PhoneNumberType getNumberTypeHelper(String nationalNumber, PhoneMetadata metadata) {
     }
     synchronized (regionToMetadataMap) {
       if (!regionToMetadataMap.containsKey(regionCode)) {
-        loadMetadataForRegionFromFile(currentFilePrefix, regionCode);
+        // The regionCode here will be valid and won't be '001', so we don't need to worry about
+        // what to pass in for the country calling code.
+        loadMetadataFromFile(currentFilePrefix, regionCode, 0);
       }
     }
     return regionToMetadataMap.get(regionCode);
   }
 
+  PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) {
+    synchronized (countryCodeToNonGeographicalMetadataMap) {
+      if (!countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode)) {
+        return null;
+      }
+      if (!countryCodeToNonGeographicalMetadataMap.containsKey(countryCallingCode)) {
+        loadMetadataFromFile(currentFilePrefix, REGION_CODE_FOR_NON_GEO_ENTITY, countryCallingCode);
+      }
+    }
+    return countryCodeToNonGeographicalMetadataMap.get(countryCallingCode);
+  }
+
   private boolean isNumberMatchingDesc(String nationalNumber, PhoneNumberDesc numberDesc) {
     Matcher possibleNumberPatternMatcher =
         regexCache.getPatternForRegex(numberDesc.getPossibleNumberPattern())
    */
   public boolean isValidNumber(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
-    return (isValidRegionCode(regionCode) && isValidNumberForRegion(number, regionCode));
+    return isValidNumberForRegion(number, regionCode);
   }
 
   /**
    * @return  a boolean that indicates whether the number is of a valid pattern
    */
   public boolean isValidNumberForRegion(PhoneNumber number, String regionCode) {
-    if (number.getCountryCode() != getCountryCodeForRegion(regionCode)) {
+    int countryCode = number.getCountryCode();
+    if (countryCode == 0 ||
+        (!REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode) &&
+         countryCode != getCountryCodeForRegion(regionCode))) {
       return false;
     }
-    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCode, regionCode);
     PhoneNumberDesc generalNumDesc = metadata.getGeneralDesc();
     String nationalSignificantNumber = getNationalSignificantNumber(number);
 
     int countryCode = number.getCountryCode();
     List<String> regions = countryCallingCodeToRegionCodeMap.get(countryCode);
     if (regions == null) {
+      String numberString = getNationalSignificantNumber(number);
+      LOGGER.log(Level.WARNING,
+                 "Missing/invalid country_code (" + countryCode + ") for number " + numberString);
       return null;
     }
     if (regions.size() == 1) {
    */
   public int getCountryCodeForRegion(String regionCode) {
     if (!isValidRegionCode(regionCode)) {
+      LOGGER.log(Level.SEVERE,
+                 "Invalid or missing region code ("
+                  + ((regionCode == null) ? "null" : regionCode)
+                  + ") provided.");
       return 0;
     }
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
    */
   public String getNddPrefixForRegion(String regionCode, boolean stripNonDigits) {
     if (!isValidRegionCode(regionCode)) {
-      LOGGER.log(Level.SEVERE, "Invalid or missing region code provided.");
+      LOGGER.log(Level.SEVERE,
+                 "Invalid or missing region code ("
+                  + ((regionCode == null) ? "null" : regionCode)
+                  + ") provided.");
       return null;
     }
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
     // Russia) since the getRegionCodeForNumber will not work if the number is possible but not
     // valid. This would need to be revisited if the possible number pattern ever differed between
     // various regions within those plans.
-    String regionCode = getRegionCodeForCountryCode(countryCode);
-    if (!isValidRegionCode(regionCode)) {
+    if (!hasValidCountryCallingCode(countryCode)) {
       return ValidationResult.INVALID_COUNTRY_CODE;
     }
-    PhoneNumberDesc generalNumDesc = getMetadataForRegion(regionCode).getGeneralDesc();
+    String regionCode = getRegionCodeForCountryCode(countryCode);
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCode, regionCode);
+    PhoneNumberDesc generalNumDesc = metadata.getGeneralDesc();
     // Handling case of numbers with no metadata.
     if (!generalNumDesc.hasNationalNumberPattern()) {
       LOGGER.log(Level.FINER, "Checking if number is possible with incomplete metadata.");
     if (countryCode != 0) {
       String phoneNumberRegion = getRegionCodeForCountryCode(countryCode);
       if (!phoneNumberRegion.equals(defaultRegion)) {
-        regionMetadata = getMetadataForRegion(phoneNumberRegion);
+        regionMetadata = getMetadataForRegionOrCallingCode(countryCode, phoneNumberRegion);
       }
     } else {
       // If no extracted country calling code, use the region supplied instead. The national number
   }
 
   /**
-   * Returns true if the number can only be dialled from within the region. If unknown, or the
-   * number can be dialled from outside the region as well, returns false. Does not check the
-   * number is a valid number.
-   * TODO: Make this method public when we have enough metadata to make it worthwhile. Currently
-   * visible for testing purposes only.
+   * Returns true if the number can be dialled from outside the region, or unknown. If the number
+   * can only be dialled from within the region, returns false. Does not check the number is a valid
+   * number.
+   * TODO: Make this method public when we have enough metadata to make it worthwhile.
    *
-   * @param number  the phone-number for which we want to know whether it is only diallable from
-   *     within the region
+   * @param number  the phone-number for which we want to know whether it is diallable from
+   *     outside the region
    */
   // @VisibleForTesting
   boolean canBeInternationallyDialled(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
-    String nationalSignificantNumber = getNationalSignificantNumber(number);
-    if (!hasValidRegionCode(regionCode, number.getCountryCode(), nationalSignificantNumber)) {
+    if (!isValidRegionCode(regionCode)) {
+      // Note numbers belonging to non-geographical entities (e.g. +800 numbers) are always
+      // internationally diallable, and will be caught here.
       return true;
     }
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    String nationalSignificantNumber = getNationalSignificantNumber(number);
     return !isNumberMatchingDesc(nationalSignificantNumber, metadata.getNoInternationalDialling());
   }
 }

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_800

Binary file added.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_808

Binary file added.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_883

Binary file added.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_888

Binary file added.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_979

Binary file added.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AR

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BH

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CI

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GA

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IL

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_JO

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_JP

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KE

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KZ

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LB

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LK

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LT

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MV

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MW

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MZ

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NA

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NL

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NZ

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RO

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SA

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SV

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TM

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UG

Binary file modified.

java/libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US

Binary file modified.

java/libphonenumber/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java

     assertEquals("+52 1 541 234 5678", formatter.inputDigit('8'));
   }
 
+  public void testAYTF_International_Toll_Free() {
+    AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter(RegionCode.US);
+    // +800 1234 5678
+    assertEquals("+", formatter.inputDigit('+'));
+    assertEquals("+8", formatter.inputDigit('8'));
+    assertEquals("+80", formatter.inputDigit('0'));
+    assertEquals("+800 ", formatter.inputDigit('0'));
+    assertEquals("+800 1", formatter.inputDigit('1'));
+    assertEquals("+800 12", formatter.inputDigit('2'));
+    assertEquals("+800 123", formatter.inputDigit('3'));
+    assertEquals("+800 1234", formatter.inputDigit('4'));
+    assertEquals("+800 1234 5", formatter.inputDigit('5'));
+    assertEquals("+800 1234 56", formatter.inputDigit('6'));
+    assertEquals("+800 1234 567", formatter.inputDigit('7'));
+    assertEquals("+800 1234 5678", formatter.inputDigit('8'));
+    assertEquals("+800123456789", formatter.inputDigit('9'));
+  }
+
   public void testAYTFMultipleLeadingDigitPatterns() {
     // +81 50 2345 6789
     AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter(RegionCode.JP);

java/libphonenumber/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java

   // countries sharing a calling code, such as the NANPA countries, the one
   // indicated with "isMainCountryForCode" in the metadata should be first.
   static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {
-    // The capacity is set to 21 as there are 16 different country codes,
+    // The capacity is set to 22 as there are 17 different country codes,
     // and this offers a load factor of roughly 0.75.
     Map<Integer, List<String>> countryCodeToRegionCodeMap =
-        new HashMap<Integer, List<String>>(21);
+        new HashMap<Integer, List<String>>(22);
 
     ArrayList<String> listWithRegionCode;
 
     listWithRegionCode.add("AD");
     countryCodeToRegionCodeMap.put(376, listWithRegionCode);
 
+    listWithRegionCode = new ArrayList<String>(1);
+    listWithRegionCode.add("001");
+    countryCodeToRegionCodeMap.put(800, listWithRegionCode);
+
     return countryCodeToRegionCodeMap;
   }
 }

java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java

     new NumberTest("31/8/2011", RegionCode.US),
     new NumberTest("1/12/2011", RegionCode.US),
     new NumberTest("10/12/82", RegionCode.DE),
+    new NumberTest("650x2531234", RegionCode.US),
   };
 
   /**
    * Strings with number-like things that should only be found under "possible".
    */
   private static final NumberTest[] POSSIBLE_ONLY_CASES = {
-    new NumberTest("abc8002345678", RegionCode.US),
     // US numbers cannot start with 7 in the test metadata to be valid.
     new NumberTest("7121115678", RegionCode.US),
     // 'X' should not be found in numbers at leniencies stricter than POSSIBLE, unless it represents
     // a carrier code or extension.
     new NumberTest("1650 x 253 - 1234", RegionCode.US),
     new NumberTest("650 x 253 - 1234", RegionCode.US),
-    new NumberTest("650x2531234", RegionCode.US),
+    new NumberTest("6502531x234", RegionCode.US),
     new NumberTest("(20) 3346 1234", RegionCode.GB),  // Non-optional NP omitted
   };
 
    * leniency level.
    */
   private static final NumberTest[] VALID_CASES = {
-    new NumberTest("65 02 53 00 00.", RegionCode.US),
+    new NumberTest("65 02 53 00 00", RegionCode.US),
     new NumberTest("6502 538365", RegionCode.US),
     new NumberTest("650//253-1234", RegionCode.US),  // 2 slashes are illegal at higher levels
     new NumberTest("650/253/1234", RegionCode.US),
     new NumberTest("9002309. 158", RegionCode.US),
-    new NumberTest("21 7/8 - 14 12/34 - 5", RegionCode.US),
+    new NumberTest("12 7/8 - 14 12/34 - 5", RegionCode.US),
     new NumberTest("12.1 - 23.71 - 23.45", RegionCode.US),
-    new NumberTest("1979-2011 100%", RegionCode.US),
+
     new NumberTest("800 234 1 111x1111", RegionCode.US),
+    new NumberTest("1979-2011 100", RegionCode.US),
     new NumberTest("+494949-4-94", RegionCode.DE),  // National number in wrong format
-    new NumberTest("\uFF14\uFF11\uFF15\uFF16\uFF16\uFF16\uFF16-\uFF17\uFF17\uFF17\uFF17",
-                   RegionCode.US),
+    new NumberTest("\uFF14\uFF11\uFF15\uFF16\uFF16\uFF16\uFF16-\uFF17\uFF17\uFF17", RegionCode.US),
+
   };
 
   /**
     new NumberTest("(33) 3461 2234", RegionCode.MX),  // Optional NP omitted
   };
 
+  public void testMatchesWithPossibleLeniency() throws Exception {
+    List<NumberTest> testCases = new ArrayList<NumberTest>();
+    testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES));
+    testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES));
+    testCases.addAll(Arrays.asList(VALID_CASES));
+    testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES));
+    doTestNumberMatchesForLeniency(testCases, Leniency.POSSIBLE);
+  }
+
+  public void testNonMatchesWithPossibleLeniency() throws Exception {
+    List<NumberTest> testCases = new ArrayList<NumberTest>();
+    testCases.addAll(Arrays.asList(IMPOSSIBLE_CASES));
+    doTestNumberNonMatchesForLeniency(testCases, Leniency.POSSIBLE);
+  }
+
+  public void testMatchesWithValidLeniency() throws Exception {
+    List<NumberTest> testCases = new ArrayList<NumberTest>();
+    testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES));
+    testCases.addAll(Arrays.asList(EXACT_GROUPING_CASES));
+    testCases.addAll(Arrays.asList(VALID_CASES));
+    doTestNumberMatchesForLeniency(testCases, Leniency.VALID);
+  }
+
+  public void testNonMatchesWithValidLeniency() throws Exception {
+    List<NumberTest> testCases = new ArrayList<NumberTest>();
+    testCases.addAll(Arrays.asList(IMPOSSIBLE_CASES));
+    testCases.addAll(Arrays.asList(POSSIBLE_ONLY_CASES));
+    doTestNumberNonMatchesForLeniency(testCases, Leniency.VALID);
+  }
+
   public void testMatchesWithStrictGroupingLeniency() throws Exception {
     List<NumberTest> testCases = new ArrayList<NumberTest>();
     testCases.addAll(Arrays.asList(STRICT_GROUPING_CASES));

java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java

   private static final PhoneNumber US_SPOOF_WITH_RAW_INPUT =
       new PhoneNumber().setCountryCode(1).setNationalNumber(0L)
           .setRawInput("000-000-0000");
+  private static final PhoneNumber INTERNATIONAL_TOLL_FREE =
+      new PhoneNumber().setCountryCode(800).setNationalNumber(12345678L);
+  private static final PhoneNumber INTERNATIONAL_TOLL_FREE_TOO_LONG =
+      new PhoneNumber().setCountryCode(800).setNationalNumber(1234567890L);
 
   public PhoneNumberUtilTest() {
     phoneUtil = initializePhoneUtilForTesting();
 
   public void testGetInstanceLoadUSMetadata() {
     PhoneMetadata metadata = phoneUtil.getMetadataForRegion(RegionCode.US);
-    assertEquals(RegionCode.US, metadata.getId());
+    assertEquals("US", metadata.getId());
     assertEquals(1, metadata.getCountryCode());
     assertEquals("011", metadata.getInternationalPrefix());
     assertTrue(metadata.hasNationalPrefix());
 
   public void testGetInstanceLoadDEMetadata() {
     PhoneMetadata metadata = phoneUtil.getMetadataForRegion(RegionCode.DE);
-    assertEquals(RegionCode.DE, metadata.getId());
+    assertEquals("DE", metadata.getId());
     assertEquals(49, metadata.getCountryCode());
     assertEquals("00", metadata.getInternationalPrefix());
     assertEquals("0", metadata.getNationalPrefix());
 
   public void testGetInstanceLoadARMetadata() {
     PhoneMetadata metadata = phoneUtil.getMetadataForRegion(RegionCode.AR);
-    assertEquals(RegionCode.AR, metadata.getId());
+    assertEquals("AR", metadata.getId());
     assertEquals(54, metadata.getCountryCode());
     assertEquals("00", metadata.getInternationalPrefix());
     assertEquals("0", metadata.getNationalPrefix());
     assertEquals("$1 $2 $3 $4", metadata.getIntlNumberFormat(3).getFormat());
   }
 
+  public void testGetInstanceLoadInternationalTollFreeMetadata() {
+    PhoneMetadata metadata = phoneUtil.getMetadataForNonGeographicalRegion(800);
+    assertEquals("001", metadata.getId());
+    assertEquals(800, metadata.getCountryCode());
+    assertEquals("$1 $2", metadata.getNumberFormat(0).getFormat());
+    assertEquals("(\\d{4})(\\d{4})", metadata.getNumberFormat(0).getPattern());
+    assertEquals("12345678", metadata.getGeneralDesc().getExampleNumber());
+    assertEquals("12345678", metadata.getTollFree().getExampleNumber());
+  }
+
   public void testIsLeadingZeroPossible() {
     assertTrue(phoneUtil.isLeadingZeroPossible(39));  // Italy
     assertFalse(phoneUtil.isLeadingZeroPossible(1));  // USA
-    assertFalse(phoneUtil.isLeadingZeroPossible(800));  // Not in metadata file, just default to
+    assertFalse(phoneUtil.isLeadingZeroPossible(800));  // International toll free numbers
+    assertFalse(phoneUtil.isLeadingZeroPossible(888));  // Not in metadata file, just default to
                                                         // false.
   }
 
 
     // An invalid US number (1 digit shorter), which has no area code.
     assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(US_SHORT_BY_ONE_NUMBER));
+
+    // An international toll free number, which has no area code.
+    assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(INTERNATIONAL_TOLL_FREE));
   }
 
   public void testGetLengthOfNationalDestinationCode() {
     // A number containing an invalid country calling code, which shouldn't have any NDC.
     PhoneNumber number = new PhoneNumber().setCountryCode(123).setNationalNumber(6502530000L);
     assertEquals(0, phoneUtil.getLengthOfNationalDestinationCode(number));
+
+    // An international toll free number, which has NDC "1234".
+    assertEquals(4, phoneUtil.getLengthOfNationalDestinationCode(INTERNATIONAL_TOLL_FREE));
   }
 
   public void testGetNationalSignificantNumber() {
 
     // An Italian fixed line number.
     assertEquals("0236618300", phoneUtil.getNationalSignificantNumber(IT_NUMBER));
+
+    assertEquals("12345678", phoneUtil.getNationalSignificantNumber(INTERNATIONAL_TOLL_FREE));
   }
 
   public void testGetExampleNumber() {
     // CS is an invalid region, so we have no data for it.
     assertNull(phoneUtil.getExampleNumberForType(RegionCode.CS,
                                                  PhoneNumberUtil.PhoneNumberType.MOBILE));
+    // RegionCode 001 is reserved for supporting non-geographical country calling code. We don't
+    // support getting an example number for it with this method.
+    assertEquals(null, phoneUtil.getExampleNumber(RegionCode.UN001));
+  }
+
+  public void testGetExampleNumberForNonGeoEntity() {
+    assertEquals(INTERNATIONAL_TOLL_FREE, phoneUtil.getExampleNumberForNonGeoEntity(800));
   }
 
   public void testConvertAlphaCharactersInNumber() {
 
     assertEquals("011 54 9 11 8765 4321",
                  phoneUtil.formatOutOfCountryCallingNumber(AR_MOBILE, RegionCode.US));
+    assertEquals("011 800 1234 5678",
+                 phoneUtil.formatOutOfCountryCallingNumber(INTERNATIONAL_TOLL_FREE, RegionCode.US));
 
     PhoneNumber arNumberWithExtn = new PhoneNumber().mergeFrom(AR_MOBILE).setExtension("1234");
     assertEquals("011 54 9 11 8765 4321 ext. 1234",
     // AQ/Antarctica isn't a valid region code for phone number formatting,
     // so this falls back to intl formatting.
     assertEquals("+1 650 253 0000",
-                 phoneUtil.formatOutOfCountryCallingNumber(US_NUMBER, "AQ"));
+                 phoneUtil.formatOutOfCountryCallingNumber(US_NUMBER, RegionCode.AQ));
+    // For region code 001, the out-of-country format always turns into the international format.
+    assertEquals("+1 650 253 0000",
+                 phoneUtil.formatOutOfCountryCallingNumber(US_NUMBER, RegionCode.UN001));
   }
 
   public void testFormatOutOfCountryWithPreferredIntlPrefix() {
     // Testing a region with multiple international prefixes.
     assertEquals("+61 1-800-SIX-FLAG",
                  phoneUtil.formatOutOfCountryKeepingAlphaChars(alphaNumericNumber, RegionCode.SG));
+    // Testing the case of calling from a non-supported region.
+    assertEquals("+61 1-800-SIX-FLAG",
+                 phoneUtil.formatOutOfCountryKeepingAlphaChars(alphaNumericNumber, RegionCode.AQ));
 
     // Testing the case with an invalid country calling code.
     alphaNumericNumber.setCountryCode(0).setNationalNumber(18007493524L)
     // No country-code stripping can be done.
     assertEquals("00 1 180-SIX",
                  phoneUtil.formatOutOfCountryKeepingAlphaChars(alphaNumericNumber, RegionCode.DE));
+
+    // Testing the case of calling from a non-supported region.
+    alphaNumericNumber.setCountryCode(1).setNationalNumber(80749L).setRawInput("180-SIX");
+    // No country-code stripping can be done since the number is invalid.
+    assertEquals("+1 180-SIX",
+                 phoneUtil.formatOutOfCountryKeepingAlphaChars(alphaNumericNumber, RegionCode.AQ));
   }
 
   public void testFormatWithCarrierCode() {
         phoneUtil.formatNumberForMobileDialing(JP_STAR_NUMBER, RegionCode.JP, false));
     assertEquals("*2345",
         phoneUtil.formatNumberForMobileDialing(JP_STAR_NUMBER, RegionCode.JP, true));
+
+    assertEquals("+80012345678",
+        phoneUtil.formatNumberForMobileDialing(INTERNATIONAL_TOLL_FREE, RegionCode.JP, false));
+    assertEquals("+800 1234 5678",
+        phoneUtil.formatNumberForMobileDialing(INTERNATIONAL_TOLL_FREE, RegionCode.JP, true));
   }
 
   public void testFormatByPattern() {
   public void testFormatE164Number() {
     assertEquals("+16502530000", phoneUtil.format(US_NUMBER, PhoneNumberFormat.E164));
     assertEquals("+4930123456", phoneUtil.format(DE_NUMBER, PhoneNumberFormat.E164));
+    assertEquals("+80012345678", phoneUtil.format(INTERNATIONAL_TOLL_FREE, PhoneNumberFormat.E164));
   }
 
   public void testFormatNumberWithExtension() {
                                                              PhoneNumberFormat.NATIONAL));
   }
 
-  public void testFormatUsingOriginalNumberFormat() throws Exception {
+  public void testFormatInOriginalFormat() throws Exception {
     PhoneNumber number1 = phoneUtil.parseAndKeepRawInput("+442087654321", RegionCode.GB);
     assertEquals("+44 20 8765 4321", phoneUtil.formatInOriginalFormat(number1, RegionCode.GB));
 
 
     // US is not a leading zero country, and the presence of the leading zero leads us to format the
     // number using raw_input.
-    PhoneNumber number7 = phoneUtil.parseAndKeepRawInput("07345678901", RegionCode.US);
-    assertEquals("07345678901", phoneUtil.formatInOriginalFormat(number7, RegionCode.US));
+    PhoneNumber number7 = phoneUtil.parseAndKeepRawInput("0734567 8901", RegionCode.US);
+    assertEquals("0734567 8901", phoneUtil.formatInOriginalFormat(number7, RegionCode.US));
 
     // This number is valid, but we don't have a formatting pattern for it. Fall back to the raw
     // input.
     PhoneNumber number8 = phoneUtil.parseAndKeepRawInput("02-4567-8900", RegionCode.KR);
     assertEquals("02-4567-8900", phoneUtil.formatInOriginalFormat(number8, RegionCode.KR));
 
+    PhoneNumber number9 = phoneUtil.parseAndKeepRawInput("01180012345678", RegionCode.US);
+    assertEquals("011 800 1234 5678", phoneUtil.formatInOriginalFormat(number9, RegionCode.US));
+
+    PhoneNumber number10 = phoneUtil.parseAndKeepRawInput("+80012345678", RegionCode.KR);
+    assertEquals("+800 1234 5678", phoneUtil.formatInOriginalFormat(number10, RegionCode.KR));
+
     // US local numbers are formatted correctly, as we have formatting patterns for them.
     PhoneNumber localNumberUS = phoneUtil.parseAndKeepRawInput("2530000", RegionCode.US);
     assertEquals("253 0000", phoneUtil.formatInOriginalFormat(localNumberUS, RegionCode.US));
+
+    PhoneNumber numberWithNationalPrefixUS =
+        phoneUtil.parseAndKeepRawInput("18003456789", RegionCode.US);
+    assertEquals("1 800 345 6789",
+        phoneUtil.formatInOriginalFormat(numberWithNationalPrefixUS, RegionCode.US));
+
+    PhoneNumber numberWithoutNationalPrefixGB =
+        phoneUtil.parseAndKeepRawInput("2087654321", RegionCode.GB);
+    assertEquals("20 8765 4321",
+        phoneUtil.formatInOriginalFormat(numberWithoutNationalPrefixGB, RegionCode.GB));
+
+    PhoneNumber numberWithNationalPrefixMX =
+        phoneUtil.parseAndKeepRawInput("013312345678", RegionCode.MX);
+    assertEquals("01 33 1234 5678",
+        phoneUtil.formatInOriginalFormat(numberWithNationalPrefixMX, RegionCode.MX));
+
+    PhoneNumber numberWithoutNationalPrefixMX =
+        phoneUtil.parseAndKeepRawInput("3312345678", RegionCode.MX);
+    assertEquals("33 1234 5678",
+        phoneUtil.formatInOriginalFormat(numberWithoutNationalPrefixMX, RegionCode.MX));
+
+    PhoneNumber italianFixedLineNumber =
+        phoneUtil.parseAndKeepRawInput("0212345678", RegionCode.IT);
+    assertEquals("02 1234 5678",
+        phoneUtil.formatInOriginalFormat(italianFixedLineNumber, RegionCode.IT));
+
+    PhoneNumber numberWithNationalPrefixJP =
+        phoneUtil.parseAndKeepRawInput("00777012", RegionCode.JP);
+    assertEquals("0077-7012",
+        phoneUtil.formatInOriginalFormat(numberWithNationalPrefixJP, RegionCode.JP));
+
+    PhoneNumber numberWithoutNationalPrefixJP =
+        phoneUtil.parseAndKeepRawInput("0777012", RegionCode.JP);
+    assertEquals("0777012",
+        phoneUtil.formatInOriginalFormat(numberWithoutNationalPrefixJP, RegionCode.JP));
+
+    PhoneNumber numberWithCarrierCodeBR =
+        phoneUtil.parseAndKeepRawInput("012 3121286979", RegionCode.BR);
+    assertEquals("012 3121286979",
+        phoneUtil.formatInOriginalFormat(numberWithCarrierCodeBR, RegionCode.BR));
+
+    // The default national prefix used in this case is 045. When a number with national prefix 044
+    // is entered, we return the raw input as we don't want to change the number entered.
+    PhoneNumber numberWithNationalPrefixMX1 =
+        phoneUtil.parseAndKeepRawInput("044(33)1234-5678", RegionCode.MX);
+    assertEquals("044(33)1234-5678",
+        phoneUtil.formatInOriginalFormat(numberWithNationalPrefixMX1, RegionCode.MX));
+
+    PhoneNumber numberWithNationalPrefixMX2 =
+        phoneUtil.parseAndKeepRawInput("045(33)1234-5678", RegionCode.MX);
+    assertEquals("045 33 1234 5678",
+        phoneUtil.formatInOriginalFormat(numberWithNationalPrefixMX2, RegionCode.MX));
+
+    // The default international prefix used in this case is 0011. When a number with international
+    // prefix 0012 is entered, we return the raw input as we don't want to change the number
+    // entered.
+    PhoneNumber outOfCountryNumberFromAU1 =
+        phoneUtil.parseAndKeepRawInput("0012 16502530000", RegionCode.AU);
+    assertEquals("0012 16502530000",
+        phoneUtil.formatInOriginalFormat(outOfCountryNumberFromAU1, RegionCode.AU));
+
+    PhoneNumber outOfCountryNumberFromAU2 =
+        phoneUtil.parseAndKeepRawInput("0011 16502530000", RegionCode.AU);
+    assertEquals("0011 1 650 253 0000",
+        phoneUtil.formatInOriginalFormat(outOfCountryNumberFromAU2, RegionCode.AU));
   }
 
   public void testIsPremiumRate() {
     tollFreeNumber.setCountryCode(49).setNationalNumber(8001234567L);
     assertEquals(PhoneNumberUtil.PhoneNumberType.TOLL_FREE,
                  phoneUtil.getNumberType(tollFreeNumber));
+
+    assertEquals(PhoneNumberUtil.PhoneNumberType.TOLL_FREE,
+                 phoneUtil.getNumberType(INTERNATIONAL_TOLL_FREE));
   }
 
   public void testIsMobile() {
     assertTrue(phoneUtil.isValidNumber(US_NUMBER));
     assertTrue(phoneUtil.isValidNumber(IT_NUMBER));
     assertTrue(phoneUtil.isValidNumber(GB_MOBILE));
+    assertTrue(phoneUtil.isValidNumber(INTERNATIONAL_TOLL_FREE));
 
     PhoneNumber nzNumber = new PhoneNumber().setCountryCode(64).setNationalNumber(21387835L);
     assertTrue(phoneUtil.isValidNumber(nzNumber));
     reNumber.setNationalNumber(800123456L);
     assertTrue(phoneUtil.isValidNumberForRegion(reNumber, RegionCode.YT));
     assertTrue(phoneUtil.isValidNumberForRegion(reNumber, RegionCode.RE));
+    assertTrue(phoneUtil.isValidNumberForRegion(INTERNATIONAL_TOLL_FREE, RegionCode.UN001));
+    assertFalse(phoneUtil.isValidNumberForRegion(INTERNATIONAL_TOLL_FREE, RegionCode.US));
   }
 
   public void testIsNotValidNumber() {
     invalidNumber.clear();
     invalidNumber.setCountryCode(64).setNationalNumber(3316005L);
     assertFalse(phoneUtil.isValidNumber(invalidNumber));
+
+    assertFalse(phoneUtil.isValidNumber(INTERNATIONAL_TOLL_FREE_TOO_LONG));
   }
 
   public void testGetRegionCodeForCountryCode() {
     assertEquals(RegionCode.US, phoneUtil.getRegionCodeForCountryCode(1));
     assertEquals(RegionCode.GB, phoneUtil.getRegionCodeForCountryCode(44));
     assertEquals(RegionCode.DE, phoneUtil.getRegionCodeForCountryCode(49));
+    assertEquals(RegionCode.UN001, phoneUtil.getRegionCodeForCountryCode(800));
   }
 
   public void testGetRegionCodeForNumber() {
     assertEquals(RegionCode.BS, phoneUtil.getRegionCodeForNumber(BS_NUMBER));
     assertEquals(RegionCode.US, phoneUtil.getRegionCodeForNumber(US_NUMBER));
     assertEquals(RegionCode.GB, phoneUtil.getRegionCodeForNumber(GB_MOBILE));
+    assertEquals(RegionCode.UN001, phoneUtil.getRegionCodeForNumber(INTERNATIONAL_TOLL_FREE));
   }
 
   public void testGetCountryCodeForRegion() {
     assertEquals(64, phoneUtil.getCountryCodeForRegion(RegionCode.NZ));
     assertEquals(0, phoneUtil.getCountryCodeForRegion(null));
     assertEquals(0, phoneUtil.getCountryCodeForRegion(RegionCode.ZZ));
+    assertEquals(0, phoneUtil.getCountryCodeForRegion(RegionCode.UN001));
     // CS is already deprecated so the library doesn't support it.
     assertEquals(0, phoneUtil.getCountryCodeForRegion(RegionCode.CS));
   }
     // Test cases with invalid regions.
     assertEquals(null, phoneUtil.getNddPrefixForRegion(null, false));
     assertEquals(null, phoneUtil.getNddPrefixForRegion(RegionCode.ZZ, false));
+    assertEquals(null, phoneUtil.getNddPrefixForRegion(RegionCode.UN001, false));
     // CS is already deprecated so the library doesn't support it.
     assertEquals(null, phoneUtil.getNddPrefixForRegion(RegionCode.CS, false));
   }
     assertTrue(phoneUtil.isNANPACountry(RegionCode.BS));
     assertFalse(phoneUtil.isNANPACountry(RegionCode.DE));
     assertFalse(phoneUtil.isNANPACountry(RegionCode.ZZ));
+    assertFalse(phoneUtil.isNANPACountry(RegionCode.UN001));
     assertFalse(phoneUtil.isNANPACountry(null));
   }
 
     assertTrue(phoneUtil.isPossibleNumber(US_NUMBER));
     assertTrue(phoneUtil.isPossibleNumber(US_LOCAL_NUMBER));
     assertTrue(phoneUtil.isPossibleNumber(GB_NUMBER));
+    assertTrue(phoneUtil.isPossibleNumber(INTERNATIONAL_TOLL_FREE));
 
     assertTrue(phoneUtil.isPossibleNumber("+1 650 253 0000", RegionCode.US));
     assertTrue(phoneUtil.isPossibleNumber("+1 650 GOO OGLE", RegionCode.US));
     assertTrue(phoneUtil.isPossibleNumber("(020) 7031 3000", RegionCode.GB));
     assertTrue(phoneUtil.isPossibleNumber("7031 3000", RegionCode.GB));
     assertTrue(phoneUtil.isPossibleNumber("3331 6005", RegionCode.NZ));
+    assertTrue(phoneUtil.isPossibleNumber("+800 1234 5678", RegionCode.UN001));
   }
 
   public void testIsPossibleNumberWithReason() {
     assertEquals(PhoneNumberUtil.ValidationResult.IS_POSSIBLE,
                  phoneUtil.isPossibleNumberWithReason(number));
 
+    assertEquals(PhoneNumberUtil.ValidationResult.TOO_LONG,
+                 phoneUtil.isPossibleNumberWithReason(INTERNATIONAL_TOLL_FREE_TOO_LONG));
+
     // Try with number that we don't have metadata for.
     PhoneNumber adNumber = new PhoneNumber();
     adNumber.setCountryCode(376).setNationalNumber(12345L);
 
   public void testIsNotPossibleNumber() {
     assertFalse(phoneUtil.isPossibleNumber(US_LONG_NUMBER));
+    assertFalse(phoneUtil.isPossibleNumber(INTERNATIONAL_TOLL_FREE_TOO_LONG));
 
     PhoneNumber number = new PhoneNumber();
     number.setCountryCode(1).setNationalNumber(253000L);
     number.clear();
     number.setCountryCode(44).setNationalNumber(300L);
     assertFalse(phoneUtil.isPossibleNumber(number));
-
     assertFalse(phoneUtil.isPossibleNumber("+1 650 253 00000", RegionCode.US));
     assertFalse(phoneUtil.isPossibleNumber("(650) 253-00000", RegionCode.US));
     assertFalse(phoneUtil.isPossibleNumber("I want a Pizza", RegionCode.US));
     assertFalse(phoneUtil.isPossibleNumber("253-000", RegionCode.US));
     assertFalse(phoneUtil.isPossibleNumber("1 3000", RegionCode.GB));
     assertFalse(phoneUtil.isPossibleNumber("+44 300", RegionCode.GB));
+    assertFalse(phoneUtil.isPossibleNumber("+800 1234 5678 9", RegionCode.UN001));
   }
 
   public void testTruncateTooLongNumber() {
-    // US number 650-253-0000, but entered with one additional digit at the end.
-    assertTrue(phoneUtil.truncateTooLongNumber(US_LONG_NUMBER));
-    assertEquals(US_NUMBER, US_LONG_NUMBER);
-
     // GB number 080 1234 5678, but entered with 4 extra digits at the end.
     PhoneNumber tooLongNumber = new PhoneNumber();
     tooLongNumber.setCountryCode(44).setNationalNumber(80123456780123L);
     assertTrue(phoneUtil.truncateTooLongNumber(tooLongNumber));
     assertEquals(validNumber, tooLongNumber);
 
+    // US number 650-253-0000, but entered with one additional digit at the end.
+    tooLongNumber.clear();
+    tooLongNumber.mergeFrom(US_LONG_NUMBER);
+    assertTrue(phoneUtil.truncateTooLongNumber(tooLongNumber));
+    assertEquals(US_NUMBER, tooLongNumber);
+
+    tooLongNumber.clear();
+    tooLongNumber.mergeFrom(INTERNATIONAL_TOLL_FREE_TOO_LONG);
+    assertTrue(phoneUtil.truncateTooLongNumber(tooLongNumber));
+    assertEquals(INTERNATIONAL_TOLL_FREE, tooLongNumber);
+
     // Tests what happens when a valid number is passed in.
     PhoneNumber validNumberCopy = new PhoneNumber().mergeFrom(validNumber);
     assertTrue(phoneUtil.truncateTooLongNumber(validNumber));
     }
     number.clear();
     try {
+      String phoneNumber = "+80012345678";
+      int countryCallingCode = 800;
+      StringBuilder numberToFill = new StringBuilder();
+      assertEquals("Did not extract country calling code " + countryCallingCode + " correctly.",
+                   countryCallingCode,
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true,
+                                                     number));
+      assertEquals("Did not figure out CountryCodeSource correctly",
+                   CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, number.getCountryCodeSource());
+    } catch (NumberParseException e) {
+      fail("Should not have thrown an exception: " + e.toString());
+    }
+    number.clear();
+    try {
       String phoneNumber = "2345-6789";
       StringBuilder numberToFill = new StringBuilder();
       assertEquals(
 
   public void testParseWithInternationalPrefixes() throws Exception {
     assertEquals(US_NUMBER, phoneUtil.parse("+1 (650) 253-0000", RegionCode.NZ));
+    assertEquals(INTERNATIONAL_TOLL_FREE, phoneUtil.parse("011 800 1234 5678", RegionCode.US));
     assertEquals(US_NUMBER, phoneUtil.parse("1-650-253-0000", RegionCode.US));
     // Calling the US number from Singapore by using different service providers
     // 1st test: calling using SingTel IDD service (IDD is 001)
     // Test with normal plus but leading characters that need to be stripped.
     assertEquals(NZ_NUMBER, phoneUtil.parse("Tel: +64 3 331 6005", RegionCode.ZZ));
     assertEquals(NZ_NUMBER, phoneUtil.parse("+64 3 331 6005", null));
+    assertEquals(INTERNATIONAL_TOLL_FREE, phoneUtil.parse("+800 1234 5678", null));
 
     // It is important that we set the carrier code to an empty string, since we used
     // ParseAndKeepRawInput and no carrier code was found.
     assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH,
                  phoneUtil.isNumberMatch("+64 3 331 6005", "+64 03 331 6005"));
     assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH,
+                 phoneUtil.isNumberMatch("+800 1234 5678", "+80012345678"));
+    assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH,
                  phoneUtil.isNumberMatch("+64 03 331-6005", "+64 03331 6005"));
     assertEquals(PhoneNumberUtil.MatchType.EXACT_MATCH,
                  phoneUtil.isNumberMatch("+643 331-6005", "+64033316005"));
     // Non-matches.
     assertEquals(PhoneNumberUtil.MatchType.NO_MATCH,
                  phoneUtil.isNumberMatch("03 331 6005", "03 331 6006"));
+    assertEquals(PhoneNumberUtil.MatchType.NO_MATCH,
+                 phoneUtil.isNumberMatch("+800 1234 5678", "+1 800 1234 5678"));
     // Different country calling code, partial number match.
     assertEquals(PhoneNumberUtil.MatchType.NO_MATCH,
                  phoneUtil.isNumberMatch("+64 3 331-6005", "+16433316005"));
 
     // We have no data for NZ - should return true.
     assertTrue(phoneUtil.canBeInternationallyDialled(NZ_NUMBER));
+    assertTrue(phoneUtil.canBeInternationallyDialled(INTERNATIONAL_TOLL_FREE));
   }
 
   public void testIsAlphaNumber() throws Exception {
     assertTrue(phoneUtil.isAlphaNumber("1800 six-flags"));
     assertTrue(phoneUtil.isAlphaNumber("1800 six-flags ext. 1234"));
+    assertTrue(phoneUtil.isAlphaNumber("+800 six-flags"));
     assertFalse(phoneUtil.isAlphaNumber("1800 123-1234"));
     assertFalse(phoneUtil.isAlphaNumber("1800 123-1234 extension: 1234"));
+    assertFalse(phoneUtil.isAlphaNumber("+800 1234-1234"));
   }
 }

java/libphonenumber/test/com/google/i18n/phonenumbers/RegionCode.java

  * Class containing string constants of region codes for easier testing.
  */
 final class RegionCode {
+  // Region code for global networks (e.g. +800 numbers).
+  static final String UN001 = "001";
   static final String AD = "AD";
   static final String AO = "AO";
+  static final String AQ = "AQ";
   static final String AR = "AR";
   static final String AU = "AU";
   static final String BR = "BR";

java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_800

Binary file added.

java/libphonenumber/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_JP

Binary file modified.

java/release_notes.txt

+January 19th, 2012: libphonenumber-4.5
+* Code changes
+ - Support for non-geographical country calling codes (e.g. +800).
+ - Modify formatInOriginalFormat to not insert/remove/modify digits in the original number passed
+   in.
+ - Fix formatOutOfCountryKeepingAlphaChars to not throw a NPE.
+
+* Metadata changes
+ - Updates for AR, BH, CI, CN, CR, DE, GA, IL, JO, JP, KE, KZ, LB, LK, LT, MV, MW, MZ, NA, NL, NZ,
+   PL, RO, SA, SV, TM, UG, US
+ - New country calling codes: 800, 808, 883, 888, 979
+ - Geocoding data updates: AR, US
+
 December 9th, 2011: libphonenumber-4.4
 * Code changes
  - Support for Voicemail numbers

resources/PhoneNumberMetaData.xml

     <!ELEMENT territory (availableFormats?, generalDesc?, noInternationalDialling?,
         areaCodeOptional?, fixedLine?, mobile?, pager?, tollFree?, premiumRate?,
         sharedCost?, personalNumber?, voip?, uan?, voicemail?, shortCode?, emergency?)>
-    <!ELEMENT generalDesc (nationalNumberPattern, possibleNumberPattern)>
+    <!ELEMENT generalDesc (nationalNumberPattern, possibleNumberPattern, exampleNumber?)>
     <!ELEMENT noInternationalDialling (nationalNumberPattern, possibleNumberPattern?,
         exampleNumber?)>
     <!ELEMENT areaCodeOptional (nationalNumberPattern, possibleNumberPattern, exampleNumber?)>
     <!ATTLIST territory mainCountryForCode (true) #IMPLIED>
     <!ATTLIST territory leadingDigits CDATA #IMPLIED>
     <!ATTLIST territory preferredInternationalPrefix CDATA #IMPLIED>
-    <!ATTLIST territory internationalPrefix CDATA #REQUIRED>
+    <!ATTLIST territory internationalPrefix CDATA #IMPLIED>
     <!ATTLIST territory nationalPrefix CDATA #IMPLIED>
     <!ATTLIST territory nationalPrefixForParsing CDATA #IMPLIED>
     <!ATTLIST territory nationalPrefixTransformRule CDATA #IMPLIED>
     <!-- http://www.itu.int/oth/T0202000009/en -->
     <!-- http://www.cnc.gov.ar/infotecnica/numeracion/Index.asp -->
     <territory id="AR" countryCode="54" internationalPrefix="00"
-               nationalPrefix="0"  nationalPrefixForParsing="0(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1-367])|3(?:[06]2|1[467]|2[02-6]|3[13-8]|[49][2-6]|5[2-8]|7)|47[3-578]|6(?:1|2[2-7]|4[6-8]?|5[125-8])|9(?:0[1-3]|[19]|2\d|3[1-6]|4[0-24-68]|5[2-4]|6[2-6]|72?|8[23]?))|3(?:3(?:2[79]|8[2578])|4(?:0[124-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6\d|7[126]|8[237-9]|9[1-36-8])|5(?:1|2[1245]|3[2-4]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|7(?:1[15-8]|2[125]|3[1245]|4[13]|5[124-8]|7[2-57]|8[1-36])|8(?:1|2[125-7]|3[23578]|4[13-6]|5[4-8]?|6[1-357-9]|7[5-8]?|8[4-7]?|9[124])))15)?"
-               nationalPrefixTransformRule="9$1" nationalPrefixFormattingRule="$NP$FG">
+        nationalPrefix="0" nationalPrefixForParsing="
+          0(?:
+            (11|
+             2(?:
+               2(?:
+                 02?|
+                 [13]|
+                 2[13-79]|
+                 4[1-6]|
+                 5[2457]|
+                 6[124-8]|
+                 7[1-4]|
+                 8[13-6]|
+                 9[1-367]
+               )|
+               3(?:
+                 [06]2|
+                 1[467]|
+                 2[02-6]|
+                 3[13-8]|
+                 [49][2-6]|
+                 5[2-8]|
+                 7
+               )|
+               47[3-578]|
+               6(?:
+                 [0136]|
+                 2[2-7]|
+                 4[6-8]?|
+                 5[15-8]
+               )|
+               80|
+               9(?:
+                 0[1-3]|
+                 [19]|
+                 2\d|
+                 3[1-6]|
+                 4[024-68]?|
+                 5[2-4]|
+                 6[2-46]|
+                 72?|
+                 8[23]?
+               )
+            )|
+            3(?:
+              3(?:
+                2[79]|
+                8[2578]
+              )|
+              4(?:
+                0[124-9]|
+                [12]|
+                3[5-8]?|
+                4[24-7]|
+                5[4-68]?|
+                6\d|
+                7[126]|
+                8[237-9]|
+                9[1-36-8]
+              )|
+              5(?:
+                1|
+                2[1245]|
+                3[2-47]?|
+                4[1-46-9]|
+                6[2-4]|
+                7[1-6]|
+                8[2-5]?
+              )|
+              6[24]|
+              7(?:
+                1[15-8]|
+                2[125]|
+                3[1245]|
+                4[13]|
+                5[14-8]|
+                [69]|
+                7[2-57]|
+                8[1-36]
+              )|
+              8(?:
+                1|
+                2[125-7]|
+                3[23578]|
+                4[13-6]|
+                5[4-8]?|
+                6[1-357-9]|
+                7[36-8]?|
+                8[5-8]?|
+                9[124]
+              )
+            )
+          )15
+        )?"
+        nationalPrefixTransformRule="9$1" nationalPrefixFormattingRule="$NP$FG">
       <availableFormats>
         <numberFormat pattern="([68]\d{2})(\d{3})(\d{4})">
           <leadingDigits>[68]</leadingDigits>
           <intlFormat>$1 $2 $3-$4</intlFormat>
         </numberFormat>
         <numberFormat pattern="(9)(\d{3})(\d{3})(\d{4})">
-          <leadingDigits>
-            9(?:
-              2[2369]|
-              3[458]
+          <!-- Some 4-digit area codes actually are caught by this rule. Preference is given however
+               to the 3-digit area codes, since they are considerably larger communities.  -->
+          <leadingDigits>
+            9(?:
+              2[23689]|
+              3[4-8]
             )
           </leadingDigits>
           <leadingDigits>
               2(?:
                 2[013]|
                 37|
-                6[14]|
-                9[179]
+                6[01346]|
+                80|
+                9[147-9]
               )|
               3(?:
                 4[1235]|
                 5[138]|
+                6[24]|
+                7[69]|
                 8[1578]
               )
             )
           </leadingDigits>
+          <!-- Note that 2944 is still formatted as a four-digit area-code and will be until 17th
+               Feb 2012 when it is migrated to the area-code 294 anyway. Several other 294X
+               four-digit area codes also exist: 2940, 2942, 2945, 2946 and 2948. -->
+          <leadingDigits>
+            9(?:
+              2(?:
+                2[013]|
+                37|
+                6[01346]|
+                80|
+                9(?:
+                  [17-9]|
+                  4[1379]
+                )
+              )|
+              3(?:
+                4[1235]|
+                5(?:
+                  [18]|
+                  3[0-35689]
+                )|
+                6[24]|
+                7[69]|
+                8(?:
+                  [15]|
+                  7[0-24-9]|
+                  8[0-79]
+                )
+              )
+            )
+          </leadingDigits>
           <format>$2 15-$3-$4</format>
           <intlFormat>$1 $2 $3-$4</intlFormat>
         </numberFormat>
+        <numberFormat pattern="(9)(\d{4})(\d{3})(\d{3})">
+          <!-- The formatting pattern here for these newly introduced area codes is based on the ITU
+               document. We have not found sufficient examples online to know if this is actually
+               being followed by the general population, or whether the back-up pattern for
+               four-digit area codes (below) is preferred. -->
+          <leadingDigits>93[58]</leadingDigits>
+          <leadingDigits>
+            9(?:
+              3(?:
+                53|
+                8[78]
+              )
+            )
+          </leadingDigits>
+          <leadingDigits>
+            9(?:
+              3(?:
+                537|