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 9ae0380..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,62 +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;
   // 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;
-  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;
-
-  // 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.
@@ -118,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>();
@@ -168,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);
 
@@ -215,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.
@@ -261,9 +269,17 @@ public class PhoneNumberUtil {
   // carrier codes, for example in Brazilian phone numbers. We also allow multiple "+" characters at
   // the start.
   // Corresponds to the following:
+  // [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 =
+      DIGITS + "{" + MIN_LENGTH_FOR_NSN + "}" + "|" +
       "[" + PLUS_CHARS + "]*+(?:[" + VALID_PUNCTUATION + STAR_SIGN + "]*" + DIGITS + "){3,}[" +
       VALID_PUNCTUATION + STAR_SIGN + VALID_ALPHA + DIGITS + "]*";
 
@@ -325,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
@@ -336,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 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 region code to the PhoneMetadata for that region.
-  private final Map<String, PhoneMetadata> regionToMetadataMap =
-      Collections.synchronizedMap(new HashMap<String, PhoneMetadata>());
-
-  // A mapping from a country calling code for a non-geographical entity to the PhoneMetadata for
-  // that country calling code. Examples of the country calling codes include 800 (International
-  // Toll Free Service) and 808 (International Shared Cost Service).
-  private final Map<Integer, PhoneMetadata> countryCodeToNonGeographicalMetadataMap =
-      Collections.synchronizedMap(new HashMap<Integer, PhoneMetadata>());
-
-  // A cache for frequently used region-specific regular expressions.
-  // As most people use phone numbers primarily from one to two countries, and there are roughly 60
-  // regular expressions needed, the initial capacity of 100 offers a rough load factor of 0.75.
-  private RegexCache regexCache = new RegexCache(100);
+  private static PhoneNumberUtil instance = null;
 
   public static final String REGION_CODE_FOR_NON_GEO_ENTITY = "001";
 
@@ -359,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.
@@ -452,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);
       }
     },
     /**
@@ -463,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
@@ -473,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);
+              }
+            });
       }
     },
     /**
@@ -528,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;
       }
     }
   }
@@ -741,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);
@@ -756,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.
@@ -821,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);
@@ -835,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.
    */
@@ -843,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();
@@ -878,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;
     }
 
@@ -903,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();
@@ -930,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;
@@ -955,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.
@@ -985,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);
@@ -998,31 +979,20 @@ public class PhoneNumberUtil {
     return normalizedNumber.toString();
   }
 
-  // @VisibleForTesting
-  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);
   }
 
   /**
@@ -1030,7 +1000,7 @@ public class PhoneNumberUtil {
    * for.
    */
   public Set<Integer> getSupportedGlobalNetworkCallingCodes() {
-    return countryCodeToNonGeographicalMetadataMap.keySet();
+    return Collections.unmodifiableSet(countryCodesForNonGeographicalRegion);
   }
 
   /**
@@ -1045,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) {
@@ -1081,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;
@@ -1101,23 +1123,25 @@ 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;
     }
-    // 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;
     }
-
+    // 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);
+    // 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(formatNsn(nationalSignificantNumber, metadata, numberFormat));
@@ -1141,13 +1165,14 @@ 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;
-    }
+    // Metadata cannot be null because the country calling code is valid
     PhoneMetadata metadata =
         getMetadataForRegionOrCallingCode(countryCallingCode, regionCode);
 
@@ -1201,16 +1226,18 @@ 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(formatNsn(nationalSignificantNumber, metadata,
                                      PhoneNumberFormat.NATIONAL, carrierCode));
     maybeAppendFormattedExtension(number, metadata, PhoneNumberFormat.NATIONAL, formattedNumber);
@@ -1269,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);
   }
 
   /**
@@ -1327,7 +1389,7 @@ public class PhoneNumberUtil {
   public String formatOutOfCountryCallingNumber(PhoneNumber number,
                                                 String regionCallingFrom) {
     if (!isValidRegionCode(regionCallingFrom)) {
-      LOGGER.log(Level.WARNING,
+      logger.log(Level.WARNING,
                  "Trying to format number from invalid region "
                  + regionCallingFrom
                  + ". International formatting applied.");
@@ -1345,14 +1407,15 @@ public class PhoneNumberUtil {
         return countryCallingCode + " " + format(number, PhoneNumberFormat.NATIONAL);
       }
     } else if (countryCallingCode == getCountryCodeForValidRegion(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
+      // 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();
 
@@ -1367,6 +1430,7 @@ 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 =
@@ -1443,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");
@@ -1477,10 +1551,14 @@ public class PhoneNumberUtil {
     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
@@ -1582,7 +1660,7 @@ public class PhoneNumberUtil {
       if (isNANPACountry(regionCallingFrom)) {
         return countryCode + " " + rawInput;
       }
-    } else if (isValidRegionCode(regionCallingFrom) &&
+    } else if (metadataForRegionCallingFrom != null &&
                countryCode == getCountryCodeForValidRegion(regionCallingFrom)) {
       NumberFormat formattingPattern =
           chooseFormattingPatternForNumber(metadataForRegionCallingFrom.numberFormats(),
@@ -1617,6 +1695,7 @@ 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);
     maybeAppendFormattedExtension(number, metadataForRegion,
                                   PhoneNumberFormat.INTERNATIONAL, formattedNumber);
@@ -1626,7 +1705,7 @@ public class PhoneNumberUtil {
     } else {
       // Invalid region entered as country-calling-from (so no metadata was found for it) or the
       // region chosen has multiple international dialling prefixes.
-      LOGGER.log(Level.WARNING,
+      logger.log(Level.WARNING,
                  "Trying to format number from invalid region "
                  + regionCallingFrom
                  + ". International formatting applied.");
@@ -1645,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();
   }
@@ -1665,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:
@@ -1699,8 +1784,8 @@ public class PhoneNumberUtil {
         : 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(
@@ -1716,13 +1801,13 @@ public class PhoneNumberUtil {
   }
 
   // Simple wrapper of formatNsnUsingPattern for the common case of no carrier code.
-  private String formatNsnUsingPattern(String nationalNumber,
-                                       NumberFormat formattingPattern,
-                                       PhoneNumberFormat numberFormat) {
+  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 formatNsnUsingPattern(String nationalNumber,
                                        NumberFormat formattingPattern,
@@ -1794,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.WARNING, "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);
@@ -1803,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;
   }
@@ -1825,10 +1910,10 @@ 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,
+      logger.log(Level.WARNING,
                  "Invalid or unknown country calling code provided: " + countryCallingCode);
     }
     return null;
@@ -1890,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;
     }
 
@@ -1948,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;
@@ -1956,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);
@@ -1968,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();
   }
 
   /**
@@ -2003,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
@@ -2018,16 +2115,7 @@ public class PhoneNumberUtil {
       // match that of the region code.
       return false;
     }
-    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;
   }
 
@@ -2044,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;
     }
@@ -2060,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())
@@ -2076,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);
@@ -2084,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.
    *
@@ -2092,7 +2195,7 @@ public class PhoneNumberUtil {
    */
   public int getCountryCodeForRegion(String regionCode) {
     if (!isValidRegionCode(regionCode)) {
-      LOGGER.log(Level.WARNING,
+      logger.log(Level.WARNING,
                  "Invalid or missing region code ("
                   + ((regionCode == null) ? "null" : regionCode)
                   + ") provided.");
@@ -2107,9 +2210,13 @@ public class PhoneNumberUtil {
    *
    * @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();
   }
 
@@ -2128,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.WARNING,
+    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) {
@@ -2164,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;
     }
@@ -2221,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>
@@ -2237,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
    */
@@ -2252,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);
   }
 
@@ -2408,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.");
@@ -2615,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;
       }
@@ -2630,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
@@ -2725,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);
@@ -2733,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
@@ -2748,17 +2876,18 @@ public class PhoneNumberUtil {
       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.");
     }
@@ -2766,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);
@@ -2804,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 {
@@ -2824,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();
@@ -2838,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
@@ -3018,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
@@ -3026,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();
+  }
 }