minor clean-up: remove some unused variables, add using and @Override annotations.
[platform/upstream/libphonenumber.git] / java / libphonenumber / src / com / google / i18n / phonenumbers / PhoneNumberUtil.java
index 1ac3933..173461c 100644 (file)
@@ -25,6 +25,7 @@ import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.ObjectInput;
 import java.io.ObjectInputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -49,57 +50,64 @@ import java.util.regex.Pattern;
  *
  * NOTE: A lot of methods in this class require Region Code strings. These must be provided using
  * ISO 3166-1 two-letter country-code format. These should be in upper-case. The list of the codes
- * can be found here: http://www.iso.org/iso/english_country_names_and_code_elements
+ * can be found here:
+ * http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm
  *
  * @author Shaopeng Jia
- * @author Lara Rennie
  */
 public class PhoneNumberUtil {
+  // @VisibleForTesting
+  static final MetadataLoader DEFAULT_METADATA_LOADER = new MetadataLoader() {
+    @Override
+    public InputStream loadMetadata(String metadataFileName) {
+      return PhoneNumberUtil.class.getResourceAsStream(metadataFileName);
+    }
+  };
+
+  private static final Logger logger = Logger.getLogger(PhoneNumberUtil.class.getName());
+
   /** Flags to use when compiling regular expressions for phone numbers. */
   static final int REGEX_FLAGS = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
   // The minimum and maximum length of the national significant number.
-  private static final int MIN_LENGTH_FOR_NSN = 3;
+  private static final int MIN_LENGTH_FOR_NSN = 2;
   // The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
-  static final int MAX_LENGTH_FOR_NSN = 16;
+  static final int MAX_LENGTH_FOR_NSN = 17;
   // The maximum length of the country calling code.
   static final int MAX_LENGTH_COUNTRY_CODE = 3;
-  static final String META_DATA_FILE_PREFIX =
-      "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto";
-  private String currentFilePrefix = META_DATA_FILE_PREFIX;
-  private static final Logger LOGGER = Logger.getLogger(PhoneNumberUtil.class.getName());
-
-  // A mapping from a country calling code to the region codes which denote the region represented
-  // by that country calling code. In the case of multiple regions sharing a calling code, such as
-  // the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
-  // first.
-  private Map<Integer, List<String>> countryCallingCodeToRegionCodeMap = null;
+  // We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
+  // input from overflowing the regular-expression engine.
+  private static final int MAX_INPUT_STRING_LENGTH = 250;
 
-  // The set of regions the library supports.
-  // 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>(320);
+  private static final String META_DATA_FILE_PREFIX =
+      "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto";
 
   // Region-code for the unknown region.
   private static final String UNKNOWN_REGION = "ZZ";
 
-  // The set of regions that share country calling code 1.
-  // There are roughly 26 regions and we set the initial capacity of the HashSet to 35 to offer a
-  // load factor of roughly 0.75.
-  private final Set<String> nanpaRegions = new HashSet<String>(35);
   private static final int NANPA_COUNTRY_CODE = 1;
 
   // The prefix that needs to be inserted in front of a Colombian landline number when dialed from
   // a mobile phone in Colombia.
   private static final String COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = "3";
 
+  // Map of country calling codes that use a mobile token before the area code. One example of when
+  // this is relevant is when determining the length of the national destination code, which should
+  // be the length of the area code plus the length of the mobile token.
+  private static final Map<Integer, String> MOBILE_TOKEN_MAPPINGS;
+
   // The PLUS_SIGN signifies the international prefix.
   static final char PLUS_SIGN = '+';
 
+  private static final char STAR_SIGN = '*';
+
   private static final String RFC3966_EXTN_PREFIX = ";ext=";
+  private static final String RFC3966_PREFIX = "tel:";
+  private static final String RFC3966_PHONE_CONTEXT = ";phone-context=";
+  private static final String RFC3966_ISDN_SUBADDRESS = ";isub=";
 
   // A map that contains characters that are essential when dialling. That means any of the
-  // characters in this map must not be removed from a number when dialing, otherwise the call will
-  // not reach the intended destination.
+  // characters in this map must not be removed from a number when dialling, otherwise the call
+  // will not reach the intended destination.
   private static final Map<Character, Character> DIALLABLE_CHAR_MAPPINGS;
 
   // Only upper-case variants of alpha characters are stored.
@@ -113,6 +121,11 @@ public class PhoneNumberUtil {
   private static final Map<Character, Character> ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
 
   static {
+    HashMap<Integer, String> mobileTokenMap = new HashMap<Integer, String>();
+    mobileTokenMap.put(52, "1");
+    mobileTokenMap.put(54, "9");
+    MOBILE_TOKEN_MAPPINGS = Collections.unmodifiableMap(mobileTokenMap);
+
     // Simple ASCII digits map used to populate ALPHA_PHONE_MAPPINGS and
     // ALL_PLUS_NUMBER_GROUPING_SYMBOLS.
     HashMap<Character, Character> asciiDigitMappings = new HashMap<Character, Character>();
@@ -163,7 +176,7 @@ public class PhoneNumberUtil {
 
     HashMap<Character, Character> diallableCharMap = new HashMap<Character, Character>();
     diallableCharMap.putAll(asciiDigitMappings);
-    diallableCharMap.put('+', '+');
+    diallableCharMap.put(PLUS_SIGN, PLUS_SIGN);
     diallableCharMap.put('*', '*');
     DIALLABLE_CHAR_MAPPINGS = Collections.unmodifiableMap(diallableCharMap);
 
@@ -210,7 +223,7 @@ public class PhoneNumberUtil {
   // placeholder for carrier information in some phone numbers. Full-width variants are also
   // present.
   static final String VALID_PUNCTUATION = "-x\u2010-\u2015\u2212\u30FC\uFF0D-\uFF0F " +
-      "\u00A0\u200B\u2060\u3000()\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E";
+      "\u00A0\u00AD\u200B\u2060\u3000()\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E";
 
   private static final String DIGITS = "\\p{Nd}";
   // We accept alpha characters in phone numbers, ASCII only, upper and lower case.
@@ -256,11 +269,19 @@ public class PhoneNumberUtil {
   // carrier codes, for example in Brazilian phone numbers. We also allow multiple "+" characters at
   // the start.
   // Corresponds to the following:
-  // plus_sign*([punctuation]*[digits]){3,}([punctuation]|[digits]|[alpha])*
+  // [digits]{minLengthNsn}|
+  // plus_sign*(([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
+  //
+  // The first reg-ex is to allow short numbers (two digits long) to be parsed if they are entered
+  // as "15" etc, but only if there is no punctuation in them. The second expression restricts the
+  // number of digits to three or more, but then allows them to be in international form, and to
+  // have alpha-characters and punctuation.
+  //
   // Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
   private static final String VALID_PHONE_NUMBER =
-      "[" + PLUS_CHARS + "]*(?:[" + VALID_PUNCTUATION + "]*" + DIGITS + "){3,}[" +
-      VALID_PUNCTUATION + VALID_ALPHA + DIGITS + "]*";
+      DIGITS + "{" + MIN_LENGTH_FOR_NSN + "}" + "|" +
+      "[" + PLUS_CHARS + "]*+(?:[" + VALID_PUNCTUATION + STAR_SIGN + "]*" + DIGITS + "){3,}[" +
+      VALID_PUNCTUATION + STAR_SIGN + VALID_ALPHA + DIGITS + "]*";
 
   // Default extension prefix to use when formatting. This will be put in front of any extension
   // component of the number, after the main national number is formatted. For example, if you wish
@@ -320,7 +341,7 @@ public class PhoneNumberUtil {
   private static final Pattern VALID_PHONE_NUMBER_PATTERN =
       Pattern.compile(VALID_PHONE_NUMBER + "(?:" + EXTN_PATTERNS_FOR_PARSING + ")?", REGEX_FLAGS);
 
-  private static final Pattern NON_DIGITS_PATTERN = Pattern.compile("(\\D+)");
+  static final Pattern NON_DIGITS_PATTERN = Pattern.compile("(\\D+)");
 
   // The FIRST_GROUP_PATTERN was originally set to $1 but there are some countries for which the
   // first group is not used in the national pattern (e.g. Argentina) so the $1 group does not match
@@ -331,22 +352,12 @@ public class PhoneNumberUtil {
   private static final Pattern FG_PATTERN = Pattern.compile("\\$FG");
   private static final Pattern CC_PATTERN = Pattern.compile("\\$CC");
 
-  private static PhoneNumberUtil instance = null;
-
-  // A mapping from a region code to the PhoneMetadata for that region.
-  private final Map<String, PhoneMetadata> regionToMetadataMap =
-      Collections.synchronizedMap(new HashMap<String, PhoneMetadata>());
+  // A pattern that is used to determine if the national prefix formatting rule has the first group
+  // only, i.e., does not start with the national prefix. Note that the pattern explicitly allows
+  // for unbalanced parentheses.
+  private static final Pattern FIRST_GROUP_ONLY_PREFIX_PATTERN = Pattern.compile("\\(?\\$1\\)?");
 
-  // 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);
+  private static PhoneNumberUtil instance = null;
 
   public static final String REGION_CODE_FOR_NON_GEO_ENTITY = "001";
 
@@ -354,9 +365,10 @@ public class PhoneNumberUtil {
    * 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
    * "+41 44 668 1800" in INTERNATIONAL format, and as "044 668 1800" in NATIONAL format.
-   * E164 format is as per INTERNATIONAL format but with no formatting applied, e.g. +41446681800.
-   * RFC3966 is as per INTERNATIONAL format, but with all spaces and other separating symbols
-   * replaced with a hyphen, and with any phone number extension appended with ";ext=".
+   * E164 format is as per INTERNATIONAL format but with no formatting applied, e.g.
+   * "+41446681800". RFC3966 is as per INTERNATIONAL format, but with all spaces and other
+   * separating symbols replaced with a hyphen, and with any phone number extension appended with
+   * ";ext=". It also will have a prefix of "tel:" added, e.g. "tel:+41-44-668-1800".
    *
    * Note: If you are considering storing the number in a neutral format, you are highly advised to
    * use the PhoneNumber class.
@@ -447,10 +459,10 @@ public class PhoneNumberUtil {
       @Override
       boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
         if (!util.isValidNumber(number) ||
-            !containsOnlyValidXChars(number, candidate, util)) {
+            !PhoneNumberMatcher.containsOnlyValidXChars(number, candidate, util)) {
           return false;
         }
-        return isNationalPrefixPresentIfRequired(number, util);
+        return PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util);
       }
     },
     /**
@@ -458,7 +470,8 @@ public class PhoneNumberUtil {
      * are grouped in a possible way for this locale. For example, a US number written as
      * "65 02 53 00 00" and "650253 0000" are not accepted at this leniency level, whereas
      * "650 253 0000", "650 2530000" or "6502530000" are.
-     * Numbers with more than one '/' symbol are also dropped at this level.
+     * Numbers with more than one '/' symbol in the national significant number are also dropped at
+     * this level.
      * <p>
      * Warning: This level might result in lower coverage especially for regions outside of country
      * code "+1". If you are not sure about which level to use, email the discussion group
@@ -468,44 +481,21 @@ public class PhoneNumberUtil {
       @Override
       boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
         if (!util.isValidNumber(number) ||
-            !containsOnlyValidXChars(number, candidate, util) ||
-            containsMoreThanOneSlash(candidate) ||
-            !isNationalPrefixPresentIfRequired(number, util)) {
+            !PhoneNumberMatcher.containsOnlyValidXChars(number, candidate, util) ||
+            PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate) ||
+            !PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util)) {
           return false;
         }
-        // TODO: Evaluate how this works for other locales (testing has been
-        // limited to NANPA regions) and optimise if necessary.
-        String[] formattedNumberGroups = getNationalNumberGroups(util, number);
-        StringBuilder normalizedCandidate = normalizeDigits(candidate,
-                                                            true /* keep strip non-digits */);
-        int fromIndex = 0;
-        // Check each group of consecutive digits are not broken into separate groups in the
-        // {@code candidate} string.
-        for (int i = 0; i < formattedNumberGroups.length; i++) {
-          // Fails if the substring of {@code candidate} starting from {@code fromIndex} doesn't
-          // contain the consecutive digits in formattedNumberGroups[i].
-          fromIndex = normalizedCandidate.indexOf(formattedNumberGroups[i], fromIndex);
-          if (fromIndex < 0) {
-            return false;
-          }
-          // Moves {@code fromIndex} forward.
-          fromIndex += formattedNumberGroups[i].length();
-          if (i == 0 && fromIndex < normalizedCandidate.length()) {
-            // We are at the position right after the NDC.
-            if (Character.isDigit(normalizedCandidate.charAt(fromIndex))) {
-              // This means there is no formatting symbol after the NDC. In this case, we only
-              // accept the number if there is no formatting symbol at all in the number, except
-              // for extensions.
-              String nationalSignificantNumber = util.getNationalSignificantNumber(number);
-              return normalizedCandidate.substring(fromIndex - formattedNumberGroups[i].length())
-                  .startsWith(nationalSignificantNumber);
-            }
-          }
-        }
-        // The check here makes sure that we haven't mistakenly already used the extension to
-        // match the last group of the subscriber number. Note the extension cannot have
-        // formatting in-between digits.
-        return normalizedCandidate.substring(fromIndex).contains(number.getExtension());
+        return PhoneNumberMatcher.checkNumberGroupingIsValid(
+            number, candidate, util, new PhoneNumberMatcher.NumberGroupingChecker() {
+              @Override
+              public boolean checkGroups(PhoneNumberUtil util, PhoneNumber number,
+                                         StringBuilder normalizedCandidate,
+                                         String[] expectedNumberGroups) {
+                return PhoneNumberMatcher.allNumberGroupsRemainGrouped(
+                    util, number, normalizedCandidate, expectedNumberGroups);
+              }
+            });
       }
     },
     /**
@@ -523,192 +513,159 @@ public class PhoneNumberUtil {
       @Override
       boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util) {
         if (!util.isValidNumber(number) ||
-            !containsOnlyValidXChars(number, candidate, util) ||
-            containsMoreThanOneSlash(candidate) ||
-            !isNationalPrefixPresentIfRequired(number, util)) {
+            !PhoneNumberMatcher.containsOnlyValidXChars(number, candidate, util) ||
+            PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate) ||
+            !PhoneNumberMatcher.isNationalPrefixPresentIfRequired(number, util)) {
           return false;
         }
-        // TODO: Evaluate how this works for other locales (testing has been
-        // limited to NANPA regions) and optimise if necessary.
-        StringBuilder normalizedCandidate = normalizeDigits(candidate,
-                                                            true /* keep strip non-digits */);
-        String[] candidateGroups =
-            NON_DIGITS_PATTERN.split(normalizedCandidate.toString());
-        // Set this to the last group, skipping it if the number has an extension.
-        int candidateNumberGroupIndex =
-            number.hasExtension() ? candidateGroups.length - 2 : candidateGroups.length - 1;
-        // First we check if the national significant number is formatted as a block.
-        // We use contains and not equals, since the national significant number may be present with
-        // a prefix such as a national number prefix, or the country code itself.
-        if (candidateGroups.length == 1 ||
-            candidateGroups[candidateNumberGroupIndex].contains(
-                util.getNationalSignificantNumber(number))) {
-          return true;
-        }
-        String[] formattedNumberGroups = getNationalNumberGroups(util, number);
-        // Starting from the end, go through in reverse, excluding the first group, and check the
-        // candidate and number groups are the same.
-        for (int formattedNumberGroupIndex = (formattedNumberGroups.length - 1);
-             formattedNumberGroupIndex > 0 && candidateNumberGroupIndex >= 0;
-             formattedNumberGroupIndex--, candidateNumberGroupIndex--) {
-          if (!candidateGroups[candidateNumberGroupIndex].equals(
-              formattedNumberGroups[formattedNumberGroupIndex])) {
-            return false;
-          }
-        }
-        // Now check the first group. There may be a national prefix at the start, so we only check
-        // that the candidate group ends with the formatted number group.
-        return (candidateNumberGroupIndex >= 0 &&
-                candidateGroups[candidateNumberGroupIndex].endsWith(formattedNumberGroups[0]));
+        return PhoneNumberMatcher.checkNumberGroupingIsValid(
+            number, candidate, util, new PhoneNumberMatcher.NumberGroupingChecker() {
+              @Override
+              public boolean checkGroups(PhoneNumberUtil util, PhoneNumber number,
+                                         StringBuilder normalizedCandidate,
+                                         String[] expectedNumberGroups) {
+                return PhoneNumberMatcher.allNumberGroupsAreExactlyPresent(
+                    util, number, normalizedCandidate, expectedNumberGroups);
+              }
+            });
       }
     };
 
-    /**
-     * Helper method to get the national-number part of a number, formatted without any national
-     * prefix, and return it as a set of digit blocks that would be formatted together.
-     */
-    private static String[] getNationalNumberGroups(PhoneNumberUtil util, PhoneNumber number) {
-      // This will be in the format +CC-DG;ext=EXT where DG represents groups of digits.
-      String rfc3966Format = util.format(number, PhoneNumberFormat.RFC3966);
-      // We remove the extension part from the formatted string before splitting it into different
-      // groups.
-      int endIndex = rfc3966Format.indexOf(';');
-      if (endIndex < 0) {
-        endIndex = rfc3966Format.length();
-      }
-      // The country-code will have a '-' following it.
-      int startIndex = rfc3966Format.indexOf('-') + 1;
-      return rfc3966Format.substring(startIndex, endIndex).split("-");
-    }
-
-    private static boolean containsMoreThanOneSlash(String candidate) {
-      int firstSlashIndex = candidate.indexOf('/');
-      return (firstSlashIndex > 0 && candidate.substring(firstSlashIndex + 1).contains("/"));
-    }
-
-    private static boolean containsOnlyValidXChars(
-        PhoneNumber number, String candidate, PhoneNumberUtil util) {
-      // The characters 'x' and 'X' can be (1) a carrier code, in which case they always precede the
-      // national significant number or (2) an extension sign, in which case they always precede the
-      // extension number. We assume a carrier code is more than 1 digit, so the first case has to
-      // have more than 1 consecutive 'x' or 'X', whereas the second case can only have exactly 1
-      // 'x' or 'X'. We ignore the character if it appears as the last character of the string.
-      for (int index = 0; index < candidate.length() - 1; index++) {
-        char charAtIndex = candidate.charAt(index);
-        if (charAtIndex == 'x' || charAtIndex == 'X') {
-          char charAtNextIndex = candidate.charAt(index + 1);
-          if (charAtNextIndex == 'x' || charAtNextIndex == 'X') {
-            // This is the carrier code case, in which the 'X's always precede the national
-            // significant number.
-            index++;
-            if (util.isNumberMatch(number, candidate.substring(index)) != MatchType.NSN_MATCH) {
-              return false;
-            }
-          // This is the extension sign case, in which the 'x' or 'X' should always precede the
-          // extension number.
-          } else if (!PhoneNumberUtil.normalizeDigitsOnly(candidate.substring(index)).equals(
-              number.getExtension())) {
-              return false;
-          }
-        }
-      }
-      return true;
-    }
-
-    private static boolean isNationalPrefixPresentIfRequired(
-        PhoneNumber number, PhoneNumberUtil util) {
-      // First, check how we deduced the country code. If it was written in international format,
-      // then the national prefix is not required.
-      if (number.getCountryCodeSource() != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
-        return true;
-      }
-      String phoneNumberRegion =
-          util.getRegionCodeForCountryCode(number.getCountryCode());
-      PhoneMetadata metadata = util.getMetadataForRegion(phoneNumberRegion);
-      if (metadata == null) {
-        return true;
-      }
-      // Check if a national prefix should be present when formatting this number.
-      String nationalNumber = util.getNationalSignificantNumber(number);
-      NumberFormat formatRule =
-          util.chooseFormattingPatternForNumber(metadata.numberFormats(), nationalNumber);
-      // To do this, we check that a national prefix formatting rule was present and that it wasn't
-      // just the first-group symbol ($1) with punctuation.
-      if ((formatRule != null) && formatRule.getNationalPrefixFormattingRule().length() > 0) {
-        if (formatRule.isNationalPrefixOptionalWhenFormatting()) {
-          // The national-prefix is optional in these cases, so we don't need to check if it was
-          // present.
-          return true;
-        }
-        // Remove the first-group symbol.
-        String candidateNationalPrefixRule = formatRule.getNationalPrefixFormattingRule();
-        // We assume that the first-group symbol will never be _before_ the national prefix.
-        candidateNationalPrefixRule =
-            candidateNationalPrefixRule.substring(0, candidateNationalPrefixRule.indexOf("$1"));
-        candidateNationalPrefixRule = util.normalizeDigitsOnly(candidateNationalPrefixRule);
-        if (candidateNationalPrefixRule.length() == 0) {
-          // National Prefix not needed for this number.
-          return true;
-        }
-        // Normalize the remainder.
-        String rawInputCopy = util.normalizeDigitsOnly(number.getRawInput());
-        StringBuilder rawInput = new StringBuilder(rawInputCopy);
-        // Check if we found a national prefix and/or carrier code at the start of the raw input,
-        // and return the result.
-        return util.maybeStripNationalPrefixAndCarrierCode(rawInput, metadata, null);
-      }
-      return true;
-    }
-
     /** Returns true if {@code number} is a verified number according to this leniency. */
     abstract boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util);
   }
 
+  // A mapping from a country calling code to the region codes which denote the region represented
+  // by that country calling code. In the case of multiple regions sharing a calling code, such as
+  // the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
+  // first.
+  private final Map<Integer, List<String>> countryCallingCodeToRegionCodeMap;
+
+  // The set of regions that share country calling code 1.
+  // There are roughly 26 regions.
+  // We set the initial capacity of the HashSet to 35 to offer a load factor of roughly 0.75.
+  private final Set<String> nanpaRegions = new HashSet<String>(35);
+
+  // A mapping from a region code to the PhoneMetadata for that region.
+  // Note: Synchronization, though only needed for the Android version of the library, is used in
+  // all versions for consistency.
+  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).
+  // Note: Synchronization, though only needed for the Android version of the library, is used in
+  // all versions for consistency.
+  private final Map<Integer, PhoneMetadata> countryCodeToNonGeographicalMetadataMap =
+      Collections.synchronizedMap(new HashMap<Integer, PhoneMetadata>());
+
+  // A cache for frequently used region-specific regular expressions.
+  // The initial capacity is set to 100 as this seems to be an optimal value for Android, based on
+  // performance measurements.
+  private final RegexCache regexCache = new RegexCache(100);
+
+  // The set of regions the library supports.
+  // 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>(320);
+
+  // The set of county calling codes that map to the non-geo entity region ("001"). This set
+  // currently contains < 12 elements so the default capacity of 16 (load factor=0.75) is fine.
+  private final Set<Integer> countryCodesForNonGeographicalRegion = new HashSet<Integer>();
+
+  // The prefix of the metadata files from which region data is loaded.
+  private final String currentFilePrefix;
+  // The metadata loader used to inject alternative metadata sources.
+  private final MetadataLoader metadataLoader;
+
   /**
-   * This class implements a singleton, so the only constructor is private.
+   * This class implements a singleton, the constructor is only visible to facilitate testing.
    */
-  private PhoneNumberUtil() {
-  }
-
-  private void init(String filePrefix) {
-    currentFilePrefix = filePrefix;
-    for (List<String> regionCodes : countryCallingCodeToRegionCodeMap.values()) {
-      supportedRegions.addAll(regionCodes);
+  // @VisibleForTesting
+  PhoneNumberUtil(String filePrefix, MetadataLoader metadataLoader,
+      Map<Integer, List<String>> countryCallingCodeToRegionCodeMap) {
+    this.currentFilePrefix = filePrefix;
+    this.metadataLoader = metadataLoader;
+    this.countryCallingCodeToRegionCodeMap = countryCallingCodeToRegionCodeMap;
+    for (Map.Entry<Integer, List<String>> entry : countryCallingCodeToRegionCodeMap.entrySet()) {
+      List<String> regionCodes = entry.getValue();
+      // We can assume that if the county calling code maps to the non-geo entity region code then
+      // that's the only region code it maps to.
+      if (regionCodes.size() == 1 && REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCodes.get(0))) {
+        // This is the subset of all country codes that map to the non-geo entity region code.
+        countryCodesForNonGeographicalRegion.add(entry.getKey());
+      } else {
+        // The supported regions set does not include the "001" non-geo entity region code.
+        supportedRegions.addAll(regionCodes);
+      }
+    }
+    // If the non-geo entity still got added to the set of supported regions it must be because
+    // there are entries that list the non-geo entity alongside normal regions (which is wrong).
+    // If we discover this, remove the non-geo entity from the set of supported regions and log.
+    if (supportedRegions.remove(REGION_CODE_FOR_NON_GEO_ENTITY)) {
+      logger.log(Level.WARNING, "invalid metadata " +
+          "(country calling code was mapped to the non-geo entity as well as specific region(s))");
     }
-    supportedRegions.remove(REGION_CODE_FOR_NON_GEO_ENTITY);
     nanpaRegions.addAll(countryCallingCodeToRegionCodeMap.get(NANPA_COUNTRY_CODE));
   }
 
-  private void loadMetadataFromFile(String filePrefix, String regionCode, int countryCallingCode) {
+  // @VisibleForTesting
+  void loadMetadataFromFile(String filePrefix, String regionCode, int countryCallingCode,
+      MetadataLoader metadataLoader) {
     boolean isNonGeoRegion = REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode);
-    InputStream source = isNonGeoRegion
-        ? PhoneNumberUtil.class.getResourceAsStream(filePrefix + "_" + countryCallingCode)
-        : PhoneNumberUtil.class.getResourceAsStream(filePrefix + "_" + regionCode);
+    String fileName = filePrefix + "_" +
+        (isNonGeoRegion ? String.valueOf(countryCallingCode) : regionCode);
+    InputStream source = metadataLoader.loadMetadata(fileName);
+    if (source == null) {
+      logger.log(Level.SEVERE, "missing metadata: " + fileName);
+      throw new IllegalStateException("missing metadata: " + fileName);
+    }
     ObjectInputStream in = null;
     try {
       in = new ObjectInputStream(source);
-      PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection();
-      metadataCollection.readExternal(in);
-      for (PhoneMetadata metadata : metadataCollection.getMetadataList()) {
-        if (isNonGeoRegion) {
-          countryCodeToNonGeographicalMetadataMap.put(countryCallingCode, metadata);
-        } else {
-          regionToMetadataMap.put(regionCode, metadata);
-        }
+      PhoneMetadataCollection metadataCollection = loadMetadataAndCloseInput(in);
+      List<PhoneMetadata> metadataList = metadataCollection.getMetadataList();
+      if (metadataList.isEmpty()) {
+        logger.log(Level.SEVERE, "empty metadata: " + fileName);
+        throw new IllegalStateException("empty metadata: " + fileName);
+      }
+      if (metadataList.size() > 1) {
+        logger.log(Level.WARNING, "invalid metadata (too many entries): " + fileName);
+      }
+      PhoneMetadata metadata = metadataList.get(0);
+      if (isNonGeoRegion) {
+        countryCodeToNonGeographicalMetadataMap.put(countryCallingCode, metadata);
+      } else {
+        regionToMetadataMap.put(regionCode, metadata);
       }
     } catch (IOException e) {
-      LOGGER.log(Level.WARNING, e.toString());
-    } finally {
-      close(in);
+      logger.log(Level.SEVERE, "cannot load/parse metadata: " + fileName, e);
+      throw new RuntimeException("cannot load/parse metadata: " + fileName, e);
     }
   }
 
-  private static void close(InputStream in) {
-    if (in != null) {
+  /**
+   * Loads the metadata protocol buffer from the given stream and closes the stream afterwards. Any
+   * exceptions that occur while reading the stream are propagated (though exceptions that occur
+   * when the stream is closed will be ignored).
+   *
+   * @param source  the non-null stream from which metadata is to be read.
+   * @return        the loaded metadata protocol buffer.
+   */
+  private static PhoneMetadataCollection loadMetadataAndCloseInput(ObjectInputStream source) {
+    PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection();
+    try {
+      metadataCollection.readExternal(source);
+    } catch (IOException e) {
+      logger.log(Level.WARNING, "error reading input (ignored)", e);
+    } finally {
       try {
-        in.close();
+        source.close();
       } catch (IOException e) {
-        LOGGER.log(Level.WARNING, e.toString());
+        logger.log(Level.WARNING, "error closing input stream (ignored)", e);
+      } finally {
+        return metadataCollection;
       }
     }
   }
@@ -736,7 +693,7 @@ public class PhoneNumberUtil {
       Matcher trailingCharsMatcher = UNWANTED_END_CHAR_PATTERN.matcher(number);
       if (trailingCharsMatcher.find()) {
         number = number.substring(0, trailingCharsMatcher.start());
-        LOGGER.log(Level.FINER, "Stripped trailing characters: " + number);
+        logger.log(Level.FINER, "Stripped trailing characters: " + number);
       }
       // Check for extra numbers at the end.
       Matcher secondNumber = SECOND_NUMBER_START_PATTERN.matcher(number);
@@ -751,7 +708,7 @@ public class PhoneNumberUtil {
 
   /**
    * Checks to see if the string of characters could possibly be a phone number at all. At the
-   * moment, checks to see that the string begins with at least 3 digits, ignoring any punctuation
+   * moment, checks to see that the string begins with at least 2 digits, ignoring any punctuation
    * commonly found in phone numbers.
    * This method does not require the number to be normalized in advance - but does assume that
    * leading non-number symbols have been removed, such as by the method extractPossibleNumber.
@@ -816,7 +773,7 @@ public class PhoneNumberUtil {
     return normalizeDigits(number, false /* strip non-digits */).toString();
   }
 
-  private static StringBuilder normalizeDigits(String number, boolean keepNonDigits) {
+  static StringBuilder normalizeDigits(String number, boolean keepNonDigits) {
     StringBuilder normalizedDigits = new StringBuilder(number.length());
     for (char c : number.toCharArray()) {
       int digit = Character.digit(c, 10);
@@ -830,6 +787,17 @@ public class PhoneNumberUtil {
   }
 
   /**
+   * Normalizes a string of characters representing a phone number. This strips all characters which
+   * are not diallable on a mobile phone keypad (including all non-ASCII digits).
+   *
+   * @param number  a string of characters representing a phone number
+   * @return        the normalized string version of the phone number
+   */
+  static String normalizeDiallableCharsOnly(String number) {
+    return normalizeHelper(number, DIALLABLE_CHAR_MAPPINGS, true /* remove non matches */);
+  }
+
+  /**
    * Converts all alpha characters in a number to their respective digits on a keypad, but retains
    * existing formatting.
    */
@@ -838,11 +806,11 @@ public class PhoneNumberUtil {
   }
 
   /**
-   * Gets the length of the geographical area code in the {@code nationalNumber_} field of the
-   * PhoneNumber object passed in, so that clients could use it to split a national significant
-   * number into geographical area code and subscriber number. It works in such a way that the
-   * resultant subscriber number should be diallable, at least on some devices. An example of how
-   * this could be used:
+   * Gets the length of the geographical area code from the
+   * PhoneNumber object passed in, so that clients could use it
+   * to split a national significant number into geographical area code and subscriber number. It
+   * works in such a way that the resultant subscriber number should be diallable, at least on some
+   * devices. An example of how this could be used:
    *
    * <pre>
    * PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
@@ -873,24 +841,23 @@ public class PhoneNumberUtil {
    *    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
-   *     code.
-   * @return  the length of area code of the PhoneNumber object passed in.
+   * @param number  the PhoneNumber object for which clients
+   *     want to know the length of the area code.
+   * @return  the length of area code of the PhoneNumber object
+   *     passed in.
    */
   public int getLengthOfGeographicalAreaCode(PhoneNumber number) {
-    String regionCode = getRegionCodeForNumber(number);
-    if (!isValidRegionCode(regionCode)) {
+    PhoneMetadata metadata = getMetadataForRegion(getRegionCodeForNumber(number));
+    if (metadata == null) {
       return 0;
     }
-    PhoneMetadata metadata = getMetadataForRegion(regionCode);
-    if (!metadata.hasNationalPrefix()) {
+    // If a country doesn't use a national prefix, and this number doesn't have an Italian leading
+    // zero, we assume it is a closed dialling plan with no area codes.
+    if (!metadata.hasNationalPrefix() && !number.isItalianLeadingZero()) {
       return 0;
     }
 
-    PhoneNumberType type = getNumberTypeHelper(getNationalSignificantNumber(number),
-                                               metadata);
-    // Most numbers other than the two types below have to be dialled in full.
-    if (type != PhoneNumberType.FIXED_LINE && type != PhoneNumberType.FIXED_LINE_OR_MOBILE) {
+    if (!isNumberGeographical(number)) {
       return 0;
     }
 
@@ -898,11 +865,12 @@ public class PhoneNumberUtil {
   }
 
   /**
-   * Gets the length of the national destination code (NDC) from the PhoneNumber object passed in,
-   * so that clients could use it to split a national significant number into NDC and subscriber
-   * number. The NDC of a phone number is normally the first group of digit(s) right after the
-   * country calling code when the number is formatted in the international format, if there is a
-   * subscriber number part that follows. An example of how this could be used:
+   * Gets the length of the national destination code (NDC) from the
+   * PhoneNumber object passed in, so that clients could use it
+   * to split a national significant number into NDC and subscriber number. The NDC of a phone
+   * number is normally the first group of digit(s) right after the country calling code when the
+   * number is formatted in the international format, if there is a subscriber number part that
+   * follows. An example of how this could be used:
    *
    * <pre>
    * PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
@@ -925,8 +893,10 @@ public class PhoneNumberUtil {
    * Refer to the unittests to see the difference between this function and
    * {@link #getLengthOfGeographicalAreaCode}.
    *
-   * @param number  the PhoneNumber object for which clients want to know the length of the NDC.
-   * @return  the length of NDC of the PhoneNumber object passed in.
+   * @param number  the PhoneNumber object for which clients
+   *     want to know the length of the NDC.
+   * @return  the length of NDC of the PhoneNumber object
+   *     passed in.
    */
   public int getLengthOfNationalDestinationCode(PhoneNumber number) {
     PhoneNumber copiedProto;
@@ -950,20 +920,36 @@ public class PhoneNumberUtil {
       return 0;
     }
 
-    if (getRegionCodeForCountryCode(number.getCountryCode()).equals("AR") &&
-        getNumberType(number) == PhoneNumberType.MOBILE) {
-      // Argentinian mobile numbers, when formatted in the international format, are in the form of
-      // +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and add 1 for
-      // the digit 9, which also forms part of the national significant number.
-      //
-      // TODO: Investigate the possibility of better modeling the metadata to make it
-      // easier to obtain the NDC.
-      return numberGroups[3].length() + 1;
+    if (getNumberType(number) == PhoneNumberType.MOBILE) {
+      // For example Argentinian mobile numbers, when formatted in the international format, are in
+      // the form of +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and
+      // add the length of the second group (which is the mobile token), which also forms part of
+      // the national significant number. This assumes that the mobile token is always formatted
+      // separately from the rest of the phone number.
+      String mobileToken = getCountryMobileToken(number.getCountryCode());
+      if (!mobileToken.equals("")) {
+        return numberGroups[2].length() + numberGroups[3].length();
+      }
     }
     return numberGroups[2].length();
   }
 
   /**
+   * Returns the mobile token for the provided country calling code if it has one, otherwise
+   * returns an empty string. A mobile token is a number inserted before the area code when dialing
+   * a mobile number from that country from abroad.
+   *
+   * @param countryCallingCode  the country calling code for which we want the mobile token
+   * @return  the mobile token, as a string, for the given country calling code
+   */
+  public static String getCountryMobileToken(int countryCallingCode) {
+    if (MOBILE_TOKEN_MAPPINGS.containsKey(countryCallingCode)) {
+      return MOBILE_TOKEN_MAPPINGS.get(countryCallingCode);
+    }
+    return "";
+  }
+
+  /**
    * Normalizes a string of characters representing a phone number by replacing all characters found
    * in the accompanying map with the values therein, and stripping all other characters if
    * removeNonMatches is true.
@@ -980,8 +966,8 @@ public class PhoneNumberUtil {
                                         Map<Character, Character> normalizationReplacements,
                                         boolean removeNonMatches) {
     StringBuilder normalizedNumber = new StringBuilder(number.length());
-    char[] numberAsCharArray = number.toCharArray();
-    for (char character : numberAsCharArray) {
+    for (int i = 0; i < number.length(); i++) {
+      char character = number.charAt(i);
       Character newDigit = normalizationReplacements.get(Character.toUpperCase(character));
       if (newDigit != null) {
         normalizedNumber.append(newDigit);
@@ -993,30 +979,28 @@ public class PhoneNumberUtil {
     return normalizedNumber.toString();
   }
 
-  static synchronized PhoneNumberUtil getInstance(
-      String baseFileLocation,
-      Map<Integer, List<String>> countryCallingCodeToRegionCodeMap) {
-    if (instance == null) {
-      instance = new PhoneNumberUtil();
-      instance.countryCallingCodeToRegionCodeMap = countryCallingCodeToRegionCodeMap;
-      instance.init(baseFileLocation);
-    }
-    return instance;
-  }
-
   /**
-   * Used for testing purposes only to reset the PhoneNumberUtil singleton to null.
+   * Sets or resets the PhoneNumberUtil singleton instance. If set to null, the next call to
+   * {@code getInstance()} will load (and return) the default instance.
    */
   // @VisibleForTesting
-  static synchronized void resetInstance() {
-    instance = null;
+  static synchronized void setInstance(PhoneNumberUtil util) {
+    instance = util;
   }
 
   /**
    * Convenience method to get a list of what regions the library has metadata for.
    */
   public Set<String> getSupportedRegions() {
-    return supportedRegions;
+    return Collections.unmodifiableSet(supportedRegions);
+  }
+
+  /**
+   * Convenience method to get a list of what global network calling codes the library has metadata
+   * for.
+   */
+  public Set<Integer> getSupportedGlobalNetworkCallingCodes() {
+    return Collections.unmodifiableSet(countryCodesForNonGeographicalRegion);
   }
 
   /**
@@ -1031,13 +1015,60 @@ public class PhoneNumberUtil {
    */
   public static synchronized PhoneNumberUtil getInstance() {
     if (instance == null) {
-      return getInstance(META_DATA_FILE_PREFIX,
-          CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap());
+      setInstance(createInstance(DEFAULT_METADATA_LOADER));
     }
     return instance;
   }
 
   /**
+   * Create a new {@link PhoneNumberUtil} instance to carry out international phone number
+   * formatting, parsing, or validation. The instance is loaded with all metadata by
+   * using the metadataLoader specified.
+   *
+   * This method should only be used in the rare case in which you want to manage your own
+   * metadata loading. Calling this method multiple times is very expensive, as each time
+   * a new instance is created from scratch. When in doubt, use {@link #getInstance}.
+   *
+   * @param metadataLoader Customized metadata loader. If null, default metadata loader will
+   *     be used. This should not be null.
+   * @return a PhoneNumberUtil instance
+   */
+  public static PhoneNumberUtil createInstance(MetadataLoader metadataLoader) {
+    if (metadataLoader == null) {
+      throw new IllegalArgumentException("metadataLoader could not be null.");
+    }
+    return new PhoneNumberUtil(META_DATA_FILE_PREFIX, metadataLoader,
+        CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap());
+  }
+
+  /**
+   * Helper function to check if the national prefix formatting rule has the first group only, i.e.,
+   * does not start with the national prefix.
+   */
+  static boolean formattingRuleHasFirstGroupOnly(String nationalPrefixFormattingRule) {
+    return nationalPrefixFormattingRule.length() == 0 ||
+        FIRST_GROUP_ONLY_PREFIX_PATTERN.matcher(nationalPrefixFormattingRule).matches();
+  }
+
+  /**
+   * Tests whether a phone number has a geographical association. It checks if the number is
+   * associated to a certain region in the country where it belongs to. Note that this doesn't
+   * verify if the number is actually in use.
+   *
+   * A similar method is implemented as PhoneNumberOfflineGeocoder.canBeGeocoded, which performs a
+   * looser check, since it only prevents cases where prefixes overlap for geocodable and
+   * non-geocodable numbers. Also, if new phone number types were added, we should check if this
+   * other method should be updated too.
+   */
+  boolean isNumberGeographical(PhoneNumber phoneNumber) {
+    PhoneNumberType numberType = getNumberType(phoneNumber);
+    // TODO: Include mobile phone numbers from countries like Indonesia, which has some
+    // mobile numbers that are geographical.
+    return numberType == PhoneNumberType.FIXED_LINE ||
+        numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE;
+  }
+
+  /**
    * Helper function to check region code is not unknown or null.
    */
   private boolean isValidRegionCode(String regionCode) {
@@ -1067,6 +1098,11 @@ public class PhoneNumberUtil {
    */
   public String format(PhoneNumber number, PhoneNumberFormat numberFormat) {
     if (number.getNationalNumber() == 0 && number.hasRawInput()) {
+      // Unparseable numbers that kept their raw input just use that.
+      // This is the only case where a number can be formatted as E164 without a
+      // leading '+' symbol (but the original number wasn't parseable anyway).
+      // TODO: Consider removing the 'if' above so that unparseable
+      // strings without raw input format to the empty string instead of "+00"
       String rawInput = number.getRawInput();
       if (rawInput.length() > 0) {
         return rawInput;
@@ -1087,27 +1123,30 @@ public class PhoneNumberUtil {
     formattedNumber.setLength(0);
     int countryCallingCode = number.getCountryCode();
     String nationalSignificantNumber = getNationalSignificantNumber(number);
+
     if (numberFormat == PhoneNumberFormat.E164) {
-      // Early exit for E164 case since no formatting of the national number needs to be applied.
-      // Extensions are not formatted.
+      // Early exit for E164 case (even if the country calling code is invalid) since no formatting
+      // of the national number needs to be applied. Extensions are not formatted.
+      formattedNumber.append(nationalSignificantNumber);
+      prefixNumberWithCountryCallingCode(countryCallingCode, PhoneNumberFormat.E164,
+                                         formattedNumber);
+      return;
+    }
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
       formattedNumber.append(nationalSignificantNumber);
-      formatNumberByFormat(countryCallingCode, PhoneNumberFormat.E164, formattedNumber);
       return;
     }
     // Note getRegionCodeForCountryCode() is used because formatting information for regions which
     // 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 (!hasValidCountryCallingCode(countryCallingCode)) {
-      formattedNumber.append(nationalSignificantNumber);
-      return;
-    }
-
+    // Metadata cannot be null because the country calling code is valid (which means that the
+    // region code cannot be ZZ and must be one of our supported region codes).
     PhoneMetadata metadata =
         getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
-    formattedNumber.append(formatNationalNumber(nationalSignificantNumber, metadata, numberFormat));
-    maybeGetFormattedExtension(number, metadata, numberFormat, formattedNumber);
-    formatNumberByFormat(countryCallingCode, numberFormat, formattedNumber);
+    formattedNumber.append(formatNsn(nationalSignificantNumber, metadata, numberFormat));
+    maybeAppendFormattedExtension(number, metadata, numberFormat, formattedNumber);
+    prefixNumberWithCountryCallingCode(countryCallingCode, numberFormat, formattedNumber);
   }
 
   /**
@@ -1126,25 +1165,32 @@ public class PhoneNumberUtil {
                                 List<NumberFormat> userDefinedFormats) {
     int countryCallingCode = number.getCountryCode();
     String nationalSignificantNumber = getNationalSignificantNumber(number);
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
+      return nationalSignificantNumber;
+    }
     // Note getRegionCodeForCountryCode() is used because formatting information for regions which
     // 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 (!hasValidCountryCallingCode(countryCallingCode)) {
-      return nationalSignificantNumber;
-    }
-    List<NumberFormat> userDefinedFormatsCopy =
-        new ArrayList<NumberFormat>(userDefinedFormats.size());
+    // Metadata cannot be null because the country calling code is valid
     PhoneMetadata metadata =
         getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
-    for (NumberFormat numFormat : userDefinedFormats) {
-      String nationalPrefixFormattingRule = numFormat.getNationalPrefixFormattingRule();
+
+    StringBuilder formattedNumber = new StringBuilder(20);
+
+    NumberFormat formattingPattern =
+        chooseFormattingPatternForNumber(userDefinedFormats, nationalSignificantNumber);
+    if (formattingPattern == null) {
+      // If no pattern above is matched, we format the number as a whole.
+      formattedNumber.append(nationalSignificantNumber);
+    } else {
+      NumberFormat numFormatCopy = new NumberFormat();
+      // Before we do a replacement of the national prefix pattern $NP with the national prefix, we
+      // need to copy the rule so that subsequent replacements for different numbers have the
+      // appropriate national prefix.
+      numFormatCopy.mergeFrom(formattingPattern);
+      String nationalPrefixFormattingRule = formattingPattern.getNationalPrefixFormattingRule();
       if (nationalPrefixFormattingRule.length() > 0) {
-        // Before we do a replacement of the national prefix pattern $NP with the national prefix,
-        // we need to copy the rule so that subsequent replacements for different numbers have the
-        // appropriate national prefix.
-        NumberFormat numFormatCopy = new NumberFormat();
-        numFormatCopy.mergeFrom(numFormat);
         String nationalPrefix = metadata.getNationalPrefix();
         if (nationalPrefix.length() > 0) {
           // Replace $NP with national prefix and $FG with the first group ($1).
@@ -1157,19 +1203,12 @@ public class PhoneNumberUtil {
           // We don't want to have a rule for how to format the national prefix if there isn't one.
           numFormatCopy.clearNationalPrefixFormattingRule();
         }
-        userDefinedFormatsCopy.add(numFormatCopy);
-      } else {
-        // Otherwise, we just add the original rule to the modified list of formats.
-        userDefinedFormatsCopy.add(numFormat);
       }
+      formattedNumber.append(
+          formatNsnUsingPattern(nationalSignificantNumber, numFormatCopy, numberFormat));
     }
-
-    StringBuilder formattedNumber =
-        new StringBuilder(formatAccordingToFormats(nationalSignificantNumber,
-                                                   userDefinedFormatsCopy,
-                                                   numberFormat));
-    maybeGetFormattedExtension(number, metadata, numberFormat, formattedNumber);
-    formatNumberByFormat(countryCallingCode, numberFormat, formattedNumber);
+    maybeAppendFormattedExtension(number, metadata, numberFormat, formattedNumber);
+    prefixNumberWithCountryCallingCode(countryCallingCode, numberFormat, formattedNumber);
     return formattedNumber.toString();
   }
 
@@ -1187,22 +1226,23 @@ public class PhoneNumberUtil {
   public String formatNationalNumberWithCarrierCode(PhoneNumber number, String carrierCode) {
     int countryCallingCode = number.getCountryCode();
     String nationalSignificantNumber = getNationalSignificantNumber(number);
+    if (!hasValidCountryCallingCode(countryCallingCode)) {
+      return nationalSignificantNumber;
+    }
+
     // Note getRegionCodeForCountryCode() is used because formatting information for regions which
     // 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 (!hasValidCountryCallingCode(countryCallingCode)) {
-      return nationalSignificantNumber;
-    }
+    // Metadata cannot be null because the country calling code is valid.
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
 
     StringBuilder formattedNumber = new StringBuilder(20);
-    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
-    formattedNumber.append(formatNationalNumber(nationalSignificantNumber,
-                                                metadata,
-                                                PhoneNumberFormat.NATIONAL,
-                                                carrierCode));
-    maybeGetFormattedExtension(number, metadata, PhoneNumberFormat.NATIONAL, formattedNumber);
-    formatNumberByFormat(countryCallingCode, PhoneNumberFormat.NATIONAL, formattedNumber);
+    formattedNumber.append(formatNsn(nationalSignificantNumber, metadata,
+                                     PhoneNumberFormat.NATIONAL, carrierCode));
+    maybeAppendFormattedExtension(number, metadata, PhoneNumberFormat.NATIONAL, formattedNumber);
+    prefixNumberWithCountryCallingCode(countryCallingCode, PhoneNumberFormat.NATIONAL,
+                                       formattedNumber);
     return formattedNumber.toString();
   }
 
@@ -1256,42 +1296,77 @@ public class PhoneNumberUtil {
       return number.hasRawInput() ? number.getRawInput() : "";
     }
 
-    String formattedNumber;
+    String formattedNumber = "";
     // 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) {
+    PhoneNumberType numberType = getNumberType(numberNoExt);
+    boolean isValidNumber = (numberType != PhoneNumberType.UNKNOWN);
+    if (regionCallingFrom.equals(regionCode)) {
+      boolean isFixedLineOrMobile =
+          (numberType == PhoneNumberType.FIXED_LINE) || (numberType == PhoneNumberType.MOBILE) ||
+          (numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
+      // Carrier codes may be needed in some countries. We handle this here.
+      if (regionCode.equals("CO") && numberType == PhoneNumberType.FIXED_LINE) {
         formattedNumber =
             formatNationalNumberWithCarrierCode(numberNoExt, COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX);
+      } else if (regionCode.equals("BR") && isFixedLineOrMobile) {
+        formattedNumber = numberNoExt.hasPreferredDomesticCarrierCode()
+            ? formattedNumber = formatNationalNumberWithPreferredCarrierCode(numberNoExt, "")
+            // Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
+            // called within Brazil. Without that, most of the carriers won't connect the call.
+            // Because of that, we return an empty string here.
+            : "";
+      } else if (isValidNumber && regionCode.equals("HU")) {
+        // The national format for HU numbers doesn't contain the national prefix, because that is
+        // how numbers are normally written down. However, the national prefix is obligatory when
+        // dialing from a mobile phone, except for short numbers. As a result, we add it back here
+        // if it is a valid regular length phone number.
+        formattedNumber =
+            getNddPrefixForRegion(regionCode, true /* strip non-digits */) +
+            " " + format(numberNoExt, PhoneNumberFormat.NATIONAL);
+      } else if (countryCallingCode == NANPA_COUNTRY_CODE) {
+        // For NANPA countries, we output international format for numbers that can be dialed
+        // internationally, since that always works, except for numbers which might potentially be
+        // short numbers, which are always dialled in national format.
+        PhoneMetadata regionMetadata = getMetadataForRegion(regionCallingFrom);
+        if (canBeInternationallyDialled(numberNoExt) &&
+            !isShorterThanPossibleNormalNumber(regionMetadata,
+                getNationalSignificantNumber(numberNoExt))) {
+          formattedNumber = format(numberNoExt, PhoneNumberFormat.INTERNATIONAL);
+        } else {
+          formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
+        }
       } else {
-        // E164 doesn't work at all when dialing within Colombia.
-        formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
+        // For non-geographical countries, and Mexican and Chilean fixed line and mobile numbers, we
+        // output international format for numbers that can be dialed internationally as that always
+        // works.
+        if ((regionCode.equals(REGION_CODE_FOR_NON_GEO_ENTITY) ||
+            // MX fixed line and mobile numbers should always be formatted in international format,
+            // even when dialed within MX. For national format to work, a carrier code needs to be
+            // used, and the correct carrier code depends on if the caller and callee are from the
+            // same local area. It is trickier to get that to work correctly than using
+            // international format, which is tested to work fine on all carriers.
+            // CL fixed line numbers need the national prefix when dialing in the national format,
+            // but don't have it when used for display. The reverse is true for mobile numbers.
+            // As a result, we output them in the international format to make it work.
+            ((regionCode.equals("MX") || regionCode.equals("CL")) &&
+             isFixedLineOrMobile)) &&
+            canBeInternationallyDialled(numberNoExt)) {
+          formattedNumber = format(numberNoExt, PhoneNumberFormat.INTERNATIONAL);
+        } else {
+          formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
+        }
       }
-    } else if (regionCode.equals("PE") && regionCallingFrom.equals("PE")) {
-      // In Peru, numbers cannot be dialled using E164 format from a mobile phone for Movistar.
-      // Instead they must be dialled in national format.
-      formattedNumber = format(numberNoExt, PhoneNumberFormat.NATIONAL);
-    } else if (regionCode.equals("BR") && regionCallingFrom.equals("BR") &&
-        ((numberType == PhoneNumberType.FIXED_LINE) || (numberType == PhoneNumberType.MOBILE) ||
-         (numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE))) {
-      formattedNumber = numberNoExt.hasPreferredDomesticCarrierCode()
-          ? formatNationalNumberWithPreferredCarrierCode(numberNoExt, "")
-          // Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
-          // called within Brazil. Without that, most of the carriers won't connect the call.
-          // Because of that, we return an empty string here.
-          : "";
-    } else if (canBeInternationallyDialled(numberNoExt)) {
+    } else if (isValidNumber && canBeInternationallyDialled(numberNoExt)) {
+      // We assume that short numbers are not diallable from outside their region, so if a number
+      // is not a valid regular length phone number, we treat it as if it cannot be internationally
+      // dialled.
       return withFormatting ? format(numberNoExt, PhoneNumberFormat.INTERNATIONAL)
                             : format(numberNoExt, PhoneNumberFormat.E164);
-    } else {
-      formattedNumber = (regionCallingFrom.equals(regionCode))
-          ? format(numberNoExt, PhoneNumberFormat.NATIONAL) : "";
     }
     return withFormatting ? formattedNumber
-                          : normalizeHelper(formattedNumber, DIALLABLE_CHAR_MAPPINGS,
-                                            true /* remove non matches */);
+                          : normalizeDiallableCharsOnly(formattedNumber);
   }
 
   /**
@@ -1314,6 +1389,10 @@ public class PhoneNumberUtil {
   public String formatOutOfCountryCallingNumber(PhoneNumber number,
                                                 String regionCallingFrom) {
     if (!isValidRegionCode(regionCallingFrom)) {
+      logger.log(Level.WARNING,
+                 "Trying to format number from invalid region "
+                 + regionCallingFrom
+                 + ". International formatting applied.");
       return format(number, PhoneNumberFormat.INTERNATIONAL);
     }
     int countryCallingCode = number.getCountryCode();
@@ -1327,15 +1406,16 @@ public class PhoneNumberUtil {
         // country calling code.
         return countryCallingCode + " " + format(number, PhoneNumberFormat.NATIONAL);
       }
-    } else if (countryCallingCode == getCountryCodeForRegion(regionCallingFrom)) {
-    // For regions that share a country calling code, the country calling code need not be dialled.
-    // This also applies when dialling within a region, so this if clause covers both these cases.
-    // Technically this is the case for dialling from La Reunion to other overseas departments of
-    // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
-    // edge case for now and for those cases return the version including country calling code.
-    // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
+    } else if (countryCallingCode == getCountryCodeForValidRegion(regionCallingFrom)) {
+      // If regions share a country calling code, the country calling code need not be dialled.
+      // This also applies when dialling within a region, so this if clause covers both these cases.
+      // Technically this is the case for dialling from La Reunion to other overseas departments of
+      // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
+      // edge case for now and for those cases return the version including country calling code.
+      // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
       return format(number, PhoneNumberFormat.NATIONAL);
     }
+    // Metadata cannot be null because we checked 'isValidRegionCode()' above.
     PhoneMetadata metadataForRegionCallingFrom = getMetadataForRegion(regionCallingFrom);
     String internationalPrefix = metadataForRegionCallingFrom.getInternationalPrefix();
 
@@ -1350,21 +1430,21 @@ public class PhoneNumberUtil {
     }
 
     String regionCode = getRegionCodeForCountryCode(countryCallingCode);
+    // Metadata cannot be null because the country calling code is valid.
     PhoneMetadata metadataForRegion =
         getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
     String formattedNationalNumber =
-        formatNationalNumber(nationalSignificantNumber,
-                             metadataForRegion, PhoneNumberFormat.INTERNATIONAL);
+        formatNsn(nationalSignificantNumber, metadataForRegion, PhoneNumberFormat.INTERNATIONAL);
     StringBuilder formattedNumber = new StringBuilder(formattedNationalNumber);
-    maybeGetFormattedExtension(number, metadataForRegion, PhoneNumberFormat.INTERNATIONAL,
-                               formattedNumber);
+    maybeAppendFormattedExtension(number, metadataForRegion, PhoneNumberFormat.INTERNATIONAL,
+                                  formattedNumber);
     if (internationalPrefixForFormatting.length() > 0) {
       formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, " ")
           .insert(0, internationalPrefixForFormatting);
     } else {
-      formatNumberByFormat(countryCallingCode,
-                           PhoneNumberFormat.INTERNATIONAL,
-                           formattedNumber);
+      prefixNumberWithCountryCallingCode(countryCallingCode,
+                                         PhoneNumberFormat.INTERNATIONAL,
+                                         formattedNumber);
     }
     return formattedNumber.toString();
   }
@@ -1379,6 +1459,7 @@ public class PhoneNumberUtil {
    *
    * 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
@@ -1426,13 +1507,23 @@ public class PhoneNumberUtil {
           formattedNumber = nationalFormat;
           break;
         }
+        // Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
+        // there is no metadata for the region.
         PhoneMetadata metadata = getMetadataForRegion(regionCode);
         String nationalNumber = getNationalSignificantNumber(number);
         NumberFormat formatRule =
             chooseFormattingPatternForNumber(metadata.numberFormats(), nationalNumber);
+        // The format rule could still be null here if the national number was 0 and there was no
+        // raw input (this should not be possible for numbers generated by the phonenumber library
+        // as they would also not have a country calling code and we would have exited earlier).
+        if (formatRule == null) {
+          formattedNumber = nationalFormat;
+          break;
+        }
         // 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.
+        // 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");
@@ -1449,19 +1540,25 @@ public class PhoneNumberUtil {
           break;
         }
         // Otherwise, we need to remove the national prefix from our output.
-        formatRule.clearNationalPrefixFormattingRule();
+        NumberFormat numFormatCopy = new NumberFormat();
+        numFormatCopy.mergeFrom(formatRule);
+        numFormatCopy.clearNationalPrefixFormattingRule();
         List<NumberFormat> numberFormats = new ArrayList<NumberFormat>(1);
-        numberFormats.add(formatRule);
+        numberFormats.add(numFormatCopy);
         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;
+    if (formattedNumber != null && rawInput.length() > 0) {
+      String normalizedFormattedNumber = normalizeDiallableCharsOnly(formattedNumber);
+      String normalizedRawInput = normalizeDiallableCharsOnly(rawInput);
+      if (!normalizedFormattedNumber.equals(normalizedRawInput)) {
+        formattedNumber = rawInput;
+      }
+    }
+    return formattedNumber;
   }
 
   // Check if rawInput, which is assumed to be in the national format, has a national prefix. The
@@ -1563,26 +1660,27 @@ public class PhoneNumberUtil {
       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>(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.
-        newFormat.setPattern("(\\d+)(.*)");
-        // Here we just concatenate them back together after the national prefix has been fixed.
-        newFormat.setFormat("$1$2");
-        availableFormats.add(newFormat);
+    } else if (metadataForRegionCallingFrom != null &&
+               countryCode == getCountryCodeForValidRegion(regionCallingFrom)) {
+      NumberFormat formattingPattern =
+          chooseFormattingPatternForNumber(metadataForRegionCallingFrom.numberFormats(),
+                                           nationalNumber);
+      if (formattingPattern == null) {
+        // If no pattern above is matched, we format the original input.
+        return rawInput;
       }
-      // Now we format using these patterns instead of the default pattern, but with the national
-      // prefix prefixed if necessary, by choosing the format rule based on the leading digits
-      // present in the unformatted national number.
+      NumberFormat newFormat = new NumberFormat();
+      newFormat.mergeFrom(formattingPattern);
+      // The first group is the first group of digits that the user wrote together.
+      newFormat.setPattern("(\\d+)(.*)");
+      // Here we just concatenate them back together after the national prefix has been fixed.
+      newFormat.setFormat("$1$2");
+      // Now we format using this pattern instead of the default pattern, but with the national
+      // prefix prefixed if necessary.
       // This will not work in the cases where the pattern (and not the leading digits) decide
       // whether a national prefix needs to be used, since we have overridden the pattern to match
       // anything, but that is not the case in the metadata to date.
-      return formatAccordingToFormats(rawInput, availableFormats, PhoneNumberFormat.NATIONAL);
+      return formatNsnUsingPattern(rawInput, newFormat, PhoneNumberFormat.NATIONAL);
     }
     String internationalPrefixForFormatting = "";
     // If an unsupported region-calling-from is entered, or a country with multiple international
@@ -1597,18 +1695,23 @@ public class PhoneNumberUtil {
     }
     StringBuilder formattedNumber = new StringBuilder(rawInput);
     String regionCode = getRegionCodeForCountryCode(countryCode);
+    // Metadata cannot be null because the country calling code is valid.
     PhoneMetadata metadataForRegion = getMetadataForRegionOrCallingCode(countryCode, regionCode);
-    maybeGetFormattedExtension(number, metadataForRegion,
-                               PhoneNumberFormat.INTERNATIONAL, formattedNumber);
+    maybeAppendFormattedExtension(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);
+      logger.log(Level.WARNING,
+                 "Trying to format number from invalid region "
+                 + regionCallingFrom
+                 + ". International formatting applied.");
+      prefixNumberWithCountryCallingCode(countryCode,
+                                         PhoneNumberFormat.INTERNATIONAL,
+                                         formattedNumber);
     }
     return formattedNumber.toString();
   }
@@ -1621,8 +1724,13 @@ public class PhoneNumberUtil {
    * @return  the national significant number of the PhoneNumber object passed in
    */
   public String getNationalSignificantNumber(PhoneNumber number) {
-    // If a leading zero has been set, we prefix this now. Note this is not a national prefix.
-    StringBuilder nationalNumber = new StringBuilder(number.isItalianLeadingZero() ? "0" : "");
+    // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
+    StringBuilder nationalNumber = new StringBuilder();
+    if (number.isItalianLeadingZero()) {
+      char[] zeros = new char[number.getNumberOfLeadingZeros()];
+      Arrays.fill(zeros, '0');
+      nationalNumber.append(new String(zeros));
+    }
     nationalNumber.append(number.getNationalNumber());
     return nationalNumber.toString();
   }
@@ -1630,9 +1738,9 @@ public class PhoneNumberUtil {
   /**
    * A helper function that is used by format and formatByPattern.
    */
-  private void formatNumberByFormat(int countryCallingCode,
-                                    PhoneNumberFormat numberFormat,
-                                    StringBuilder formattedNumber) {
+  private void prefixNumberWithCountryCallingCode(int countryCallingCode,
+                                                  PhoneNumberFormat numberFormat,
+                                                  StringBuilder formattedNumber) {
     switch (numberFormat) {
       case E164:
         formattedNumber.insert(0, countryCallingCode).insert(0, PLUS_SIGN);
@@ -1641,7 +1749,8 @@ public class PhoneNumberUtil {
         formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, PLUS_SIGN);
         return;
       case RFC3966:
-        formattedNumber.insert(0, "-").insert(0, countryCallingCode).insert(0, PLUS_SIGN);
+        formattedNumber.insert(0, "-").insert(0, countryCallingCode).insert(0, PLUS_SIGN)
+            .insert(0, RFC3966_PREFIX);
         return;
       case NATIONAL:
       default:
@@ -1649,21 +1758,19 @@ public class PhoneNumberUtil {
     }
   }
 
-  // Simple wrapper of formatNationalNumber for the common case of no carrier code.
-  private String formatNationalNumber(String number,
-                                      PhoneMetadata metadata,
-                                      PhoneNumberFormat numberFormat) {
-    return formatNationalNumber(number, metadata, numberFormat, null);
+  // Simple wrapper of formatNsn for the common case of no carrier code.
+  private String formatNsn(String number, PhoneMetadata metadata, PhoneNumberFormat numberFormat) {
+    return formatNsn(number, metadata, numberFormat, null);
   }
 
   // Note in some regions, the national number can be written in two completely different ways
   // depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The
   // numberFormat parameter here is used to specify which format to use for those cases. If a
   // carrierCode is specified, this will be inserted into the formatted string to replace $CC.
-  private String formatNationalNumber(String number,
-                                      PhoneMetadata metadata,
-                                      PhoneNumberFormat numberFormat,
-                                      String carrierCode) {
+  private String formatNsn(String number,
+                           PhoneMetadata metadata,
+                           PhoneNumberFormat numberFormat,
+                           String carrierCode) {
     List<NumberFormat> intlNumberFormats = metadata.intlNumberFormats();
     // When the intlNumberFormats exists, we use that to format national number for the
     // INTERNATIONAL format instead of using the numberDesc.numberFormats.
@@ -1671,17 +1778,14 @@ public class PhoneNumberUtil {
         (intlNumberFormats.size() == 0 || numberFormat == PhoneNumberFormat.NATIONAL)
         ? metadata.numberFormats()
         : metadata.intlNumberFormats();
-    String formattedNationalNumber =
-        formatAccordingToFormats(number, availableFormats, numberFormat, carrierCode);
-    if (numberFormat == PhoneNumberFormat.RFC3966) {
-      formattedNationalNumber =
-          SEPARATOR_PATTERN.matcher(formattedNationalNumber).replaceAll("-");
-    }
-    return formattedNationalNumber;
+    NumberFormat formattingPattern = chooseFormattingPatternForNumber(availableFormats, number);
+    return (formattingPattern == null)
+        ? number
+        : formatNsnUsingPattern(number, formattingPattern, numberFormat, carrierCode);
   }
 
-  private NumberFormat chooseFormattingPatternForNumber(List<NumberFormat> availableFormats,
-                                                        String nationalNumber) {
+  NumberFormat chooseFormattingPatternForNumber(List<NumberFormat> availableFormats,
+                                                String nationalNumber) {
     for (NumberFormat numFormat : availableFormats) {
       int size = numFormat.leadingDigitsPatternSize();
       if (size == 0 || regexCache.getPatternForRegex(
@@ -1696,50 +1800,58 @@ public class PhoneNumberUtil {
     return null;
   }
 
-  // Simple wrapper of formatAccordingToFormats for the common case of no carrier code.
-  private String formatAccordingToFormats(String nationalNumber,
-                                          List<NumberFormat> availableFormats,
-                                          PhoneNumberFormat numberFormat) {
-    return formatAccordingToFormats(nationalNumber, availableFormats, numberFormat, null);
+  // Simple wrapper of formatNsnUsingPattern for the common case of no carrier code.
+  String formatNsnUsingPattern(String nationalNumber,
+                               NumberFormat formattingPattern,
+                               PhoneNumberFormat numberFormat) {
+    return formatNsnUsingPattern(nationalNumber, formattingPattern, numberFormat, null);
   }
 
-  // Note that carrierCode is optional - if NULL or an empty string, no carrier code replacement
+  // Note that carrierCode is optional - if null or an empty string, no carrier code replacement
   // will take place.
-  private String formatAccordingToFormats(String nationalNumber,
-                                          List<NumberFormat> availableFormats,
-                                          PhoneNumberFormat numberFormat,
-                                          String carrierCode) {
-    NumberFormat numFormat = chooseFormattingPatternForNumber(availableFormats, nationalNumber);
-    if (numFormat == null) {
-      // If no pattern above is matched, we format the number as a whole.
-      return nationalNumber;
-    }
-    String numberFormatRule = numFormat.getFormat();
-    Matcher m = regexCache.getPatternForRegex(numFormat.getPattern()).matcher(nationalNumber);
+  private String formatNsnUsingPattern(String nationalNumber,
+                                       NumberFormat formattingPattern,
+                                       PhoneNumberFormat numberFormat,
+                                       String carrierCode) {
+    String numberFormatRule = formattingPattern.getFormat();
+    Matcher m =
+        regexCache.getPatternForRegex(formattingPattern.getPattern()).matcher(nationalNumber);
+    String formattedNationalNumber = "";
     if (numberFormat == PhoneNumberFormat.NATIONAL &&
         carrierCode != null && carrierCode.length() > 0 &&
-        numFormat.getDomesticCarrierCodeFormattingRule().length() > 0) {
+        formattingPattern.getDomesticCarrierCodeFormattingRule().length() > 0) {
       // Replace the $CC in the formatting rule with the desired carrier code.
-      String carrierCodeFormattingRule = numFormat.getDomesticCarrierCodeFormattingRule();
+      String carrierCodeFormattingRule = formattingPattern.getDomesticCarrierCodeFormattingRule();
       carrierCodeFormattingRule =
           CC_PATTERN.matcher(carrierCodeFormattingRule).replaceFirst(carrierCode);
       // Now replace the $FG in the formatting rule with the first group and the carrier code
       // combined in the appropriate way.
       numberFormatRule = FIRST_GROUP_PATTERN.matcher(numberFormatRule)
           .replaceFirst(carrierCodeFormattingRule);
-      return m.replaceAll(numberFormatRule);
+      formattedNationalNumber = m.replaceAll(numberFormatRule);
     } else {
       // Use the national prefix formatting rule instead.
-      String nationalPrefixFormattingRule = numFormat.getNationalPrefixFormattingRule();
+      String nationalPrefixFormattingRule = formattingPattern.getNationalPrefixFormattingRule();
       if (numberFormat == PhoneNumberFormat.NATIONAL &&
           nationalPrefixFormattingRule != null &&
           nationalPrefixFormattingRule.length() > 0) {
         Matcher firstGroupMatcher = FIRST_GROUP_PATTERN.matcher(numberFormatRule);
-        return m.replaceAll(firstGroupMatcher.replaceFirst(nationalPrefixFormattingRule));
+        formattedNationalNumber =
+            m.replaceAll(firstGroupMatcher.replaceFirst(nationalPrefixFormattingRule));
       } else {
-        return m.replaceAll(numberFormatRule);
+        formattedNationalNumber = m.replaceAll(numberFormatRule);
+      }
+    }
+    if (numberFormat == PhoneNumberFormat.RFC3966) {
+      // Strip any leading punctuation.
+      Matcher matcher = SEPARATOR_PATTERN.matcher(formattedNationalNumber);
+      if (matcher.lookingAt()) {
+        formattedNationalNumber = matcher.replaceFirst("");
       }
+      // Replace the rest with a dash between each number group.
+      formattedNationalNumber = matcher.reset(formattedNationalNumber).replaceAll("-");
     }
+    return formattedNationalNumber;
   }
 
   /**
@@ -1767,7 +1879,7 @@ public class PhoneNumberUtil {
   public PhoneNumber getExampleNumberForType(String regionCode, PhoneNumberType type) {
     // Check the region code is valid.
     if (!isValidRegionCode(regionCode)) {
-      LOGGER.log(Level.SEVERE, "Invalid or unknown region code provided: " + regionCode);
+      logger.log(Level.WARNING, "Invalid or unknown region code provided: " + regionCode);
       return null;
     }
     PhoneNumberDesc desc = getNumberDescByType(getMetadataForRegion(regionCode), type);
@@ -1776,7 +1888,7 @@ public class PhoneNumberUtil {
         return parse(desc.getExampleNumber(), regionCode);
       }
     } catch (NumberParseException e) {
-      LOGGER.log(Level.SEVERE, e.toString());
+      logger.log(Level.SEVERE, e.toString());
     }
     return null;
   }
@@ -1798,8 +1910,11 @@ public class PhoneNumberUtil {
           return parse("+" + countryCallingCode + desc.getExampleNumber(), "ZZ");
         }
       } catch (NumberParseException e) {
-        LOGGER.log(Level.SEVERE, e.toString());
+        logger.log(Level.SEVERE, e.toString());
       }
+    } else {
+      logger.log(Level.WARNING,
+                 "Invalid or unknown country calling code provided: " + countryCallingCode);
     }
     return null;
   }
@@ -1808,32 +1923,22 @@ public class PhoneNumberUtil {
    * Appends the formatted extension of a phone number to formattedNumber, if the phone number had
    * an extension specified.
    */
-  private void maybeGetFormattedExtension(PhoneNumber number, PhoneMetadata metadata,
-                                          PhoneNumberFormat numberFormat,
-                                          StringBuilder formattedNumber) {
+  private void maybeAppendFormattedExtension(PhoneNumber number, PhoneMetadata metadata,
+                                             PhoneNumberFormat numberFormat,
+                                             StringBuilder formattedNumber) {
     if (number.hasExtension() && number.getExtension().length() > 0) {
       if (numberFormat == PhoneNumberFormat.RFC3966) {
         formattedNumber.append(RFC3966_EXTN_PREFIX).append(number.getExtension());
       } else {
-        formatExtension(number.getExtension(), metadata, formattedNumber);
+        if (metadata.hasPreferredExtnPrefix()) {
+          formattedNumber.append(metadata.getPreferredExtnPrefix()).append(number.getExtension());
+        } else {
+          formattedNumber.append(DEFAULT_EXTN_PREFIX).append(number.getExtension());
+        }
       }
     }
   }
 
-  /**
-   * Formats the extension part of the phone number by prefixing it with the appropriate extension
-   * prefix. This will be the default extension prefix, unless overridden by a preferred
-   * extension prefix for this region.
-   */
-  private void formatExtension(String extensionDigits, PhoneMetadata metadata,
-                               StringBuilder extension) {
-    if (metadata.hasPreferredExtnPrefix()) {
-      extension.append(metadata.getPreferredExtnPrefix()).append(extensionDigits);
-    } else {
-      extension.append(DEFAULT_EXTN_PREFIX).append(extensionDigits);
-    }
-  }
-
   PhoneNumberDesc getNumberDescByType(PhoneMetadata metadata, PhoneNumberType type) {
     switch (type) {
       case PREMIUM_RATE:
@@ -1870,18 +1975,16 @@ public class PhoneNumberUtil {
    */
   public PhoneNumberType getNumberType(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
-    if (!isValidRegionCode(regionCode) && !REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode)) {
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(number.getCountryCode(), regionCode);
+    if (metadata == null) {
       return PhoneNumberType.UNKNOWN;
     }
     String nationalSignificantNumber = getNationalSignificantNumber(number);
-    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(number.getCountryCode(), regionCode);
     return getNumberTypeHelper(nationalSignificantNumber, metadata);
   }
 
   private PhoneNumberType getNumberTypeHelper(String nationalNumber, PhoneMetadata metadata) {
-    PhoneNumberDesc generalNumberDesc = metadata.getGeneralDesc();
-    if (!generalNumberDesc.hasNationalNumberPattern() ||
-        !isNumberMatchingDesc(nationalNumber, generalNumberDesc)) {
+    if (!isNumberMatchingDesc(nationalNumber, metadata.getGeneralDesc())) {
       return PhoneNumberType.UNKNOWN;
     }
 
@@ -1928,6 +2031,10 @@ public class PhoneNumberUtil {
     return PhoneNumberType.UNKNOWN;
   }
 
+  /**
+   * Returns the metadata for the given region code or {@code null} if the region code is invalid
+   * or unknown.
+   */
   PhoneMetadata getMetadataForRegion(String regionCode) {
     if (!isValidRegionCode(regionCode)) {
       return null;
@@ -1936,7 +2043,7 @@ public class PhoneNumberUtil {
       if (!regionToMetadataMap.containsKey(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);
+        loadMetadataFromFile(currentFilePrefix, regionCode, 0, metadataLoader);
       }
     }
     return regionToMetadataMap.get(regionCode);
@@ -1948,20 +2055,26 @@ public class PhoneNumberUtil {
         return null;
       }
       if (!countryCodeToNonGeographicalMetadataMap.containsKey(countryCallingCode)) {
-        loadMetadataFromFile(currentFilePrefix, REGION_CODE_FOR_NON_GEO_ENTITY, countryCallingCode);
+        loadMetadataFromFile(
+            currentFilePrefix, REGION_CODE_FOR_NON_GEO_ENTITY, countryCallingCode, metadataLoader);
       }
     }
     return countryCodeToNonGeographicalMetadataMap.get(countryCallingCode);
   }
 
-  private boolean isNumberMatchingDesc(String nationalNumber, PhoneNumberDesc numberDesc) {
+  boolean isNumberPossibleForDesc(String nationalNumber, PhoneNumberDesc numberDesc) {
     Matcher possibleNumberPatternMatcher =
         regexCache.getPatternForRegex(numberDesc.getPossibleNumberPattern())
             .matcher(nationalNumber);
+    return possibleNumberPatternMatcher.matches();
+  }
+
+  boolean isNumberMatchingDesc(String nationalNumber, PhoneNumberDesc numberDesc) {
     Matcher nationalNumberPatternMatcher =
         regexCache.getPatternForRegex(numberDesc.getNationalNumberPattern())
             .matcher(nationalNumber);
-    return possibleNumberPatternMatcher.matches() && nationalNumberPatternMatcher.matches();
+    return isNumberPossibleForDesc(nationalNumber, numberDesc) &&
+        nationalNumberPatternMatcher.matches();
   }
 
   /**
@@ -1983,6 +2096,10 @@ public class PhoneNumberUtil {
    * immediately exits with false. After this, the specific number pattern rules for the region are
    * examined. This is useful for determining for example whether a particular number is valid for
    * Canada, rather than just a valid NANPA number.
+   * Warning: In most cases, you want to use {@link #isValidNumber} instead. For example, this
+   * method will mark numbers from British Crown dependencies such as the Isle of Man as invalid for
+   * the region "GB" (United Kingdom), since it has its own region code, "IM", which may be
+   * undesirable.
    *
    * @param number       the phone number that we want to validate
    * @param regionCode   the region that we want to validate the phone number for
@@ -1990,22 +2107,15 @@ public class PhoneNumberUtil {
    */
   public boolean isValidNumberForRegion(PhoneNumber number, String regionCode) {
     int countryCode = number.getCountryCode();
-    if (countryCode == 0 ||
+    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCode, regionCode);
+    if ((metadata == null) ||
         (!REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode) &&
-         countryCode != getCountryCodeForRegion(regionCode))) {
+         countryCode != getCountryCodeForValidRegion(regionCode))) {
+      // Either the region code was invalid, or the country calling code for this number does not
+      // match that of the region code.
       return false;
     }
-    PhoneMetadata metadata = getMetadataForRegionOrCallingCode(countryCode, regionCode);
-    PhoneNumberDesc generalNumDesc = metadata.getGeneralDesc();
     String nationalSignificantNumber = getNationalSignificantNumber(number);
-
-    // For regions where we don't have metadata for PhoneNumberDesc, we treat any number passed in
-    // as a valid number if its national significant number is between the minimum and maximum
-    // lengths defined by ITU for a national significant number.
-    if (!generalNumDesc.hasNationalNumberPattern()) {
-      int numberLength = nationalSignificantNumber.length();
-      return numberLength > MIN_LENGTH_FOR_NSN && numberLength <= MAX_LENGTH_FOR_NSN;
-    }
     return getNumberTypeHelper(nationalSignificantNumber, metadata) != PhoneNumberType.UNKNOWN;
   }
 
@@ -2022,7 +2132,7 @@ public class PhoneNumberUtil {
     List<String> regions = countryCallingCodeToRegionCodeMap.get(countryCode);
     if (regions == null) {
       String numberString = getNationalSignificantNumber(number);
-      LOGGER.log(Level.WARNING,
+      logger.log(Level.WARNING,
                  "Missing/invalid country_code (" + countryCode + ") for number " + numberString);
       return null;
     }
@@ -2038,6 +2148,7 @@ public class PhoneNumberUtil {
     String nationalNumber = getNationalSignificantNumber(number);
     for (String regionCode : regionCodes) {
       // If leadingDigits is present, use this. Otherwise, do full validation.
+      // Metadata cannot be null because the region codes come from the country calling code map.
       PhoneMetadata metadata = getMetadataForRegion(regionCode);
       if (metadata.hasLeadingDigits()) {
         if (regexCache.getPatternForRegex(metadata.getLeadingDigits())
@@ -2054,7 +2165,10 @@ public class PhoneNumberUtil {
   /**
    * Returns the region code that matches the specific country calling code. In the case of no
    * region code being found, ZZ will be returned. In the case of multiple regions, the one
-   * designated in the metadata as the "main" region for this calling code will be returned.
+   * designated in the metadata as the "main" region for this calling code will be returned. If the
+   * countryCallingCode entered is valid but doesn't match a specific region (such as in the case of
+   * non-geographical calling codes like 800) the value "001" will be returned (corresponding to
+   * the value for World in the UN M.49 schema).
    */
   public String getRegionCodeForCountryCode(int countryCallingCode) {
     List<String> regionCodes = countryCallingCodeToRegionCodeMap.get(countryCallingCode);
@@ -2062,6 +2176,17 @@ public class PhoneNumberUtil {
   }
 
   /**
+   * Returns a list with the region codes that match the specific country calling code. For
+   * non-geographical country calling codes, the region code 001 is returned. Also, in the case
+   * of no region code being found, an empty list is returned.
+   */
+  public List<String> getRegionCodesForCountryCode(int countryCallingCode) {
+    List<String> regionCodes = countryCallingCodeToRegionCodeMap.get(countryCallingCode);
+    return Collections.unmodifiableList(regionCodes == null ? new ArrayList<String>(0)
+                                                            : regionCodes);
+  }
+
+  /**
    * Returns the country calling code for a specific region. For example, this would be 1 for the
    * United States, and 64 for New Zealand.
    *
@@ -2070,13 +2195,28 @@ public class PhoneNumberUtil {
    */
   public int getCountryCodeForRegion(String regionCode) {
     if (!isValidRegionCode(regionCode)) {
-      LOGGER.log(Level.SEVERE,
+      logger.log(Level.WARNING,
                  "Invalid or missing region code ("
                   + ((regionCode == null) ? "null" : regionCode)
                   + ") provided.");
       return 0;
     }
+    return getCountryCodeForValidRegion(regionCode);
+  }
+
+  /**
+   * Returns the country calling code for a specific region. For example, this would be 1 for the
+   * United States, and 64 for New Zealand. Assumes the region is already valid.
+   *
+   * @param regionCode  the region that we want to get the country calling code for
+   * @return  the country calling code for the region denoted by regionCode
+   * @throws IllegalArgumentException if the region is invalid
+   */
+  private int getCountryCodeForValidRegion(String regionCode) {
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    if (metadata == null) {
+      throw new IllegalArgumentException("Invalid region code: " + regionCode);
+    }
     return metadata.getCountryCode();
   }
 
@@ -2095,14 +2235,14 @@ public class PhoneNumberUtil {
    * @return  the dialling prefix for the region denoted by regionCode
    */
   public String getNddPrefixForRegion(String regionCode, boolean stripNonDigits) {
-    if (!isValidRegionCode(regionCode)) {
-      LOGGER.log(Level.SEVERE,
+    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    if (metadata == null) {
+      logger.log(Level.WARNING,
                  "Invalid or missing region code ("
                   + ((regionCode == null) ? "null" : regionCode)
                   + ") provided.");
       return null;
     }
-    PhoneMetadata metadata = getMetadataForRegion(regionCode);
     String nationalPrefix = metadata.getNationalPrefix();
     // If no national prefix was found, we return null.
     if (nationalPrefix.length() == 0) {
@@ -2131,8 +2271,9 @@ public class PhoneNumberUtil {
    * metadata for the country is found.
    */
   boolean isLeadingZeroPossible(int countryCallingCode) {
-    PhoneMetadata mainMetadataForCallingCode = getMetadataForRegion(
-        getRegionCodeForCountryCode(countryCallingCode));
+    PhoneMetadata mainMetadataForCallingCode =
+        getMetadataForRegionOrCallingCode(countryCallingCode,
+                                          getRegionCodeForCountryCode(countryCallingCode));
     if (mainMetadataForCallingCode == null) {
       return false;
     }
@@ -2188,6 +2329,17 @@ public class PhoneNumberUtil {
   }
 
   /**
+   * Helper method to check whether a number is too short to be a regular length phone number in a
+   * region.
+   */
+  private boolean isShorterThanPossibleNormalNumber(PhoneMetadata regionMetadata, String number) {
+    Pattern possibleNumberPattern = regexCache.getPatternForRegex(
+        regionMetadata.getGeneralDesc().getPossibleNumberPattern());
+    return testNumberLengthAgainstPattern(possibleNumberPattern, number) ==
+        ValidationResult.TOO_SHORT;
+  }
+
+  /**
    * Check whether a phone number is a possible number. It provides a more lenient check than
    * {@link #isValidNumber} in the following sense:
    *<ol>
@@ -2204,7 +2356,7 @@ public class PhoneNumberUtil {
    *      numbers, that would most likely be area codes) and length (obviously includes the
    *      length of area codes for fixed line numbers), it will return false for the
    *      subscriber-number-only version.
-   * </ol
+   * </ol>
    * @param number  the number that needs to be checked
    * @return  a ValidationResult object which indicates whether the number is possible
    */
@@ -2219,22 +2371,10 @@ public class PhoneNumberUtil {
       return ValidationResult.INVALID_COUNTRY_CODE;
     }
     String regionCode = getRegionCodeForCountryCode(countryCode);
+    // Metadata cannot be null because the country calling code is valid.
     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.");
-      int numberLength = nationalNumber.length();
-      if (numberLength < MIN_LENGTH_FOR_NSN) {
-        return ValidationResult.TOO_SHORT;
-      } else if (numberLength > MAX_LENGTH_FOR_NSN) {
-        return ValidationResult.TOO_LONG;
-      } else {
-        return ValidationResult.IS_POSSIBLE;
-      }
-    }
     Pattern possibleNumberPattern =
-        regexCache.getPatternForRegex(generalNumDesc.getPossibleNumberPattern());
+        regexCache.getPatternForRegex(metadata.getGeneralDesc().getPossibleNumberPattern());
     return testNumberLengthAgainstPattern(possibleNumberPattern, nationalNumber);
   }
 
@@ -2375,7 +2515,7 @@ public class PhoneNumberUtil {
       phoneNumber.setCountryCodeSource(countryCodeSource);
     }
     if (countryCodeSource != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
-      if (fullNumber.length() < MIN_LENGTH_FOR_NSN) {
+      if (fullNumber.length() <= MIN_LENGTH_FOR_NSN) {
         throw new NumberParseException(NumberParseException.ErrorType.TOO_SHORT_AFTER_IDD,
                                        "Phone number had an IDD, but after this was not "
                                        + "long enough to be a viable phone number.");
@@ -2582,7 +2722,7 @@ public class PhoneNumberUtil {
   private boolean checkRegionForParsing(String numberToParse, String defaultRegion) {
     if (!isValidRegionCode(defaultRegion)) {
       // If the number is null or empty, we can't infer the region.
-      if (numberToParse == null || numberToParse.length() == 0 ||
+      if ((numberToParse == null) || (numberToParse.length() == 0) ||
           !PLUS_CHARS_PATTERN.matcher(numberToParse).lookingAt()) {
         return false;
       }
@@ -2597,7 +2737,8 @@ public class PhoneNumberUtil {
    * particular region is not performed. This can be done separately with {@link #isValidNumber}.
    *
    * @param numberToParse     number that we are attempting to parse. This can contain formatting
-   *                          such as +, ( and -, as well as a phone number extension.
+   *                          such as +, ( and -, as well as a phone number extension. It can also
+   *                          be provided in RFC3966 format.
    * @param defaultRegion     region that we are expecting the number to be from. This is only used
    *                          if the number being parsed is not written in international format.
    *                          The country_code for the number in this case would be stored as that
@@ -2692,6 +2833,7 @@ public class PhoneNumberUtil {
       final long maxTries) {
 
     return new Iterable<PhoneNumberMatch>() {
+      @Override
       public Iterator<PhoneNumberMatch> iterator() {
         return new PhoneNumberMatcher(
             PhoneNumberUtil.this, text, defaultRegion, leniency, maxTries);
@@ -2700,6 +2842,25 @@ public class PhoneNumberUtil {
   }
 
   /**
+   * A helper function to set the values related to leading zeros in a PhoneNumber.
+   */
+  static void setItalianLeadingZerosForPhoneNumber(String nationalNumber, PhoneNumber phoneNumber) {
+    if (nationalNumber.length() > 1 && nationalNumber.charAt(0) == '0') {
+      phoneNumber.setItalianLeadingZero(true);
+      int numberOfLeadingZeros = 1;
+      // Note that if the national number is all "0"s, the last "0" is not counted as a leading
+      // zero.
+      while (numberOfLeadingZeros < nationalNumber.length() - 1 &&
+             nationalNumber.charAt(numberOfLeadingZeros) == '0') {
+        numberOfLeadingZeros++;
+      }
+      if (numberOfLeadingZeros != 1) {
+        phoneNumber.setNumberOfLeadingZeros(numberOfLeadingZeros);
+      }
+    }
+  }
+
+  /**
    * Parses a string and fills up the phoneNumber. This method is the same as the public
    * parse() method, with the exception that it allows the default region to be null, for use by
    * isNumberMatch(). checkRegion should be set to false if it is permitted for the default region
@@ -2711,18 +2872,22 @@ public class PhoneNumberUtil {
     if (numberToParse == null) {
       throw new NumberParseException(NumberParseException.ErrorType.NOT_A_NUMBER,
                                      "The phone number supplied was null.");
+    } else if (numberToParse.length() > MAX_INPUT_STRING_LENGTH) {
+      throw new NumberParseException(NumberParseException.ErrorType.TOO_LONG,
+                                     "The string supplied was too long to parse.");
     }
-    // Extract a possible number from the string passed in (this strips leading characters that
-    // could not be the start of a phone number.)
-    String number = extractPossibleNumber(numberToParse);
-    if (!isViablePhoneNumber(number)) {
+
+    StringBuilder nationalNumber = new StringBuilder();
+    buildNationalNumberForParsing(numberToParse, nationalNumber);
+
+    if (!isViablePhoneNumber(nationalNumber.toString())) {
       throw new NumberParseException(NumberParseException.ErrorType.NOT_A_NUMBER,
                                      "The string supplied did not seem to be a phone number.");
     }
 
     // Check the region supplied is valid, or that the extracted number starts with some sort of +
     // sign so the number's region can be determined.
-    if (checkRegion && !checkRegionForParsing(number, defaultRegion)) {
+    if (checkRegion && !checkRegionForParsing(nationalNumber.toString(), defaultRegion)) {
       throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE,
                                      "Missing or invalid default region.");
     }
@@ -2730,7 +2895,6 @@ public class PhoneNumberUtil {
     if (keepRawInput) {
       phoneNumber.setRawInput(numberToParse);
     }
-    StringBuilder nationalNumber = new StringBuilder(number);
     // Attempt to parse extension first, since it doesn't require region-specific data and we want
     // to have the non-normalised number here.
     String extension = maybeStripExtension(nationalNumber);
@@ -2768,6 +2932,7 @@ public class PhoneNumberUtil {
     if (countryCode != 0) {
       String phoneNumberRegion = getRegionCodeForCountryCode(countryCode);
       if (!phoneNumberRegion.equals(defaultRegion)) {
+        // Metadata cannot be null because the country calling code is valid.
         regionMetadata = getMetadataForRegionOrCallingCode(countryCode, phoneNumberRegion);
       }
     } else {
@@ -2788,9 +2953,16 @@ public class PhoneNumberUtil {
     }
     if (regionMetadata != null) {
       StringBuilder carrierCode = new StringBuilder();
-      maybeStripNationalPrefixAndCarrierCode(normalizedNationalNumber, regionMetadata, carrierCode);
-      if (keepRawInput) {
-        phoneNumber.setPreferredDomesticCarrierCode(carrierCode.toString());
+      StringBuilder potentialNationalNumber = new StringBuilder(normalizedNationalNumber);
+      maybeStripNationalPrefixAndCarrierCode(potentialNationalNumber, regionMetadata, carrierCode);
+      // We require that the NSN remaining after stripping the national prefix and carrier code be
+      // of a possible length for the region. Otherwise, we don't do the stripping, since the
+      // original number could be a valid short number.
+      if (!isShorterThanPossibleNormalNumber(regionMetadata, potentialNationalNumber.toString())) {
+        normalizedNationalNumber = potentialNationalNumber;
+        if (keepRawInput) {
+          phoneNumber.setPreferredDomesticCarrierCode(carrierCode.toString());
+        }
       }
     }
     int lengthOfNationalNumber = normalizedNationalNumber.length();
@@ -2802,13 +2974,59 @@ public class PhoneNumberUtil {
       throw new NumberParseException(NumberParseException.ErrorType.TOO_LONG,
                                      "The string supplied is too long to be a phone number.");
     }
-    if (normalizedNationalNumber.charAt(0) == '0') {
-      phoneNumber.setItalianLeadingZero(true);
-    }
+    setItalianLeadingZerosForPhoneNumber(normalizedNationalNumber.toString(), phoneNumber);
     phoneNumber.setNationalNumber(Long.parseLong(normalizedNationalNumber.toString()));
   }
 
   /**
+   * Converts numberToParse to a form that we can parse and write it to nationalNumber if it is
+   * written in RFC3966; otherwise extract a possible number out of it and write to nationalNumber.
+   */
+  private void buildNationalNumberForParsing(String numberToParse, StringBuilder nationalNumber) {
+    int indexOfPhoneContext = numberToParse.indexOf(RFC3966_PHONE_CONTEXT);
+    if (indexOfPhoneContext > 0) {
+      int phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT.length();
+      // If the phone context contains a phone number prefix, we need to capture it, whereas domains
+      // will be ignored.
+      if (numberToParse.charAt(phoneContextStart) == PLUS_SIGN) {
+        // Additional parameters might follow the phone context. If so, we will remove them here
+        // because the parameters after phone context are not important for parsing the
+        // phone number.
+        int phoneContextEnd = numberToParse.indexOf(';', phoneContextStart);
+        if (phoneContextEnd > 0) {
+          nationalNumber.append(numberToParse.substring(phoneContextStart, phoneContextEnd));
+        } else {
+          nationalNumber.append(numberToParse.substring(phoneContextStart));
+        }
+      }
+
+      // Now append everything between the "tel:" prefix and the phone-context. This should include
+      // the national number, an optional extension or isdn-subaddress component. Note we also
+      // handle the case when "tel:" is missing, as we have seen in some of the phone number inputs.
+      // In that case, we append everything from the beginning.
+      int indexOfRfc3966Prefix = numberToParse.indexOf(RFC3966_PREFIX);
+      int indexOfNationalNumber = (indexOfRfc3966Prefix >= 0) ?
+          indexOfRfc3966Prefix + RFC3966_PREFIX.length() : 0;
+      nationalNumber.append(numberToParse.substring(indexOfNationalNumber, indexOfPhoneContext));
+    } else {
+      // Extract a possible number from the string passed in (this strips leading characters that
+      // could not be the start of a phone number.)
+      nationalNumber.append(extractPossibleNumber(numberToParse));
+    }
+
+    // Delete the isdn-subaddress and everything after it if it is present. Note extension won't
+    // appear at the same time with isdn-subaddress according to paragraph 5.3 of the RFC3966 spec,
+    int indexOfIsdn = nationalNumber.indexOf(RFC3966_ISDN_SUBADDRESS);
+    if (indexOfIsdn > 0) {
+      nationalNumber.delete(indexOfIsdn, nationalNumber.length());
+    }
+    // If both phone context and isdn-subaddress are absent but other parameters are present, the
+    // parameters are left in nationalNumber. This is because we are concerned about deleting
+    // content from a potential number string when there is no strong evidence that the number is
+    // actually written in RFC3966.
+  }
+
+  /**
    * Takes two phone numbers and compares them for equality.
    *
    * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero for Italian numbers
@@ -2982,7 +3200,7 @@ public class PhoneNumberUtil {
   /**
    * 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.
+   * number. Note that, at the moment, this method does not handle short numbers.
    * 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 diallable from
@@ -2990,14 +3208,29 @@ public class PhoneNumberUtil {
    */
   // @VisibleForTesting
   boolean canBeInternationallyDialled(PhoneNumber number) {
-    String regionCode = getRegionCodeForNumber(number);
-    if (!isValidRegionCode(regionCode)) {
+    PhoneMetadata metadata = getMetadataForRegion(getRegionCodeForNumber(number));
+    if (metadata == null) {
       // 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());
   }
+
+  /**
+   * Returns true if the supplied region supports mobile number portability. Returns false for
+   * invalid, unknown or regions that don't support mobile number portability.
+   *
+   * @param regionCode  the region for which we want to know whether it supports mobile number
+   *                    portability or not.
+   */
+  public boolean isMobileNumberPortableRegion(String regionCode) {
+    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    if (metadata == null) {
+      logger.log(Level.WARNING, "Invalid or unknown region code provided: " + regionCode);
+      return false;
+    }
+    return metadata.isMobileNumberPortableRegion();
+  }
 }