Update external/libphonenumber to v3.6.

Bug: 4520583, 4595580

TODO: delete
java/src/com/google/i18n/phonenumbers/PhoneNumberOfflineGeocoding
once code in Phone App is updated to use the offline geocoder in the new
location under j/s/c/g/i/phonenumbers/geocoding/

Change-Id: I3a3e6db84ed0d24290b1be19651fa9a82de4cc39
diff --git a/README.android b/README.android
index 026ab5d..d9fa3b7 100644
--- a/README.android
+++ b/README.android
@@ -1,4 +1,4 @@
 URL: http://code.google.com/p/libphonenumber/
-Version: r206
+Version: 3.6 (r264)
 License: Apache 2
 Description: Google Phone Number Library.
diff --git a/java/build.xml b/java/build.xml
index d1768e6..63398b5 100644
--- a/java/build.xml
+++ b/java/build.xml
@@ -4,7 +4,6 @@
   <property name="src.dir" value="src"/>
   <property name="test.dir" value="test"/>
   <property name="build.dir" value="build"/>
-  <property name="resources.dir" value="resources"/>
   <property name="classes.dir" value="${build.dir}/classes"/>
   <property name="jar.dir" value="${build.dir}/jar"/>
   <property name="lib.dir" value="lib"/>
@@ -22,11 +21,57 @@
     </fileset>
   </path>
 
-  <target name="compile" description="Compile Java source.">
+  <target name="build-metadata">
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar"/>
+      <arg value="BuildMetadataProtoFromXml"/>
+      <arg value="../resources/PhoneNumberMetaData.xml"/>
+      <arg value="src"/>
+      <arg value="false"/> <!-- Not for testing. -->
+      <arg value="false"/> <!-- No lite metadata. -->
+    </exec>
+  </target>
+
+  <target name="build-test-metadata">
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar"/>
+      <arg value="BuildMetadataProtoFromXml"/>
+      <arg value="../resources/PhoneNumberMetaDataForTesting.xml"/>
+      <arg value="test"/>
+      <arg value="true"/> <!-- For testing. -->
+      <arg value="false"/> <!-- No lite metadata. -->
+    </exec>
+  </target>
+
+  <target name="build-geo-data">
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar"/>
+      <arg value="GenerateAreaCodeData"/>
+      <arg value="../resources/geocoding/"/>
+      <arg value="src/com/google/i18n/phonenumbers/geocoding/data"/>
+      <arg value="false"/> <!-- Not for testing. -->
+    </exec>
+  </target>
+
+  <target name="build-geo-test-data">
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar"/>
+      <arg value="GenerateAreaCodeData"/>
+      <arg value="../resources/test/geocoding/"/>
+      <arg value="test/com/google/i18n/phonenumbers/geocoding/testing_data"/>
+      <arg value="false"/> <!-- Not for testing. -->
+    </exec>
+  </target>
+
+  <target name="compile" description="Compile Java source."
+          depends="build-metadata,build-geo-data">
     <mkdir dir="${classes.dir}"/>
     <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
     <javac srcdir="${test.dir}" destdir="${classes.dir}" classpathref="classpath" debug="on"/>
-    <javac srcdir="${resources.dir}" destdir="${classes.dir}" classpathref="classpath"/>
   </target>
 
   <target name="jar" depends="compile">
@@ -35,21 +80,33 @@
       <fileset dir="${classes.dir}">
         <include name="**/*.class"/>
         <exclude name="**/*Test*"/>
-        <exclude name="**/*Build*"/>
+        <exclude name="**/BuildMetadata*"/>
+        <exclude name="**/JSArrayBuilder*"/>
+        <exclude name="**/geocoding/*"/>
       </fileset>
       <fileset dir="${src.dir}">
         <include name="**/PhoneNumberMetadataProto*"/>
       </fileset>
     </jar>
+    <jar destfile="${jar.dir}/offline-geocoder.jar">
+      <fileset dir="${classes.dir}">
+        <include name="**/geocoding/*.class"/>
+        <exclude name="**/*Test*"/>
+        <exclude name="**/geocoding/GenerateAreaCodeData*"/>
+      </fileset>
+      <fileset dir="${src.dir}">
+        <include name="**/geocoding/data/*"/>
+      </fileset>
+    </jar>
   </target>
 
-  <target name="test-jar" depends="compile">
+  <target name="test-jar"
+          depends="compile,build-test-metadata,build-geo-test-data">
     <mkdir dir="${jar.dir}"/>
     <jar destfile="${jar.dir}/${ant.project.name}-test.jar">
       <fileset dir="${classes.dir}">
         <include name="**/*.class"/>
         <exclude name="**/*Test*"/>
-        <exclude name="**/*Build*"/>
       </fileset>
       <fileset dir="${src.dir}">
         <include name="**/PhoneNumberMetadataProto*"/>
@@ -57,6 +114,9 @@
       <fileset dir="${test.dir}">
         <include name="**/PhoneNumberMetadataProtoForTesting*"/>
       </fileset>
+      <fileset dir="${test.dir}">
+        <include name="**/geocoding/testing_data/*"/>
+      </fileset>
     </jar>
   </target>
 
diff --git a/java/lib/junit-4.8.1.jar b/java/lib/junit-4.8.1.jar
deleted file mode 100644
index 524cd65..0000000
--- a/java/lib/junit-4.8.1.jar
+++ /dev/null
Binary files differ
diff --git a/java/pom.xml b/java/pom.xml
index 92134a6..e376262 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -3,7 +3,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.googlecode.libphonenumber</groupId>
   <artifactId>libphonenumber</artifactId>
-  <version>3.5-SNAPSHOT</version>
+  <version>3.7-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>libphonenumber</name>
   <url>http://code.google.com/p/libphonenumber/</url>
@@ -37,6 +37,10 @@
     <url>scm:svn:http://libphonenumber.googlecode.com/svn/trunk/java/</url>
   </scm>
 
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
   <developers>
     <developer>
       <id>jia.shao.peng</id>
@@ -88,10 +92,24 @@
         <directory>test/com/google/i18n/phonenumbers/data</directory>
         <targetPath>com/google/i18n/phonenumbers/data</targetPath>
       </testResource>
+      <testResource>
+        <directory>test/com/google/i18n/phonenumbers/geocoding/testing_data</directory>
+        <targetPath>com/google/i18n/phonenumbers/geocoding/testing_data</targetPath>
+      </testResource>
     </testResources>
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.3.1</version>
+        <configuration>
+          <excludes>
+            <exclude>**/geocoding/</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-source-plugin</artifactId>
         <version>2.1.2</version>
         <executions>
@@ -165,12 +183,97 @@
         </plugins>
       </build>
     </profile>
+    <!-- Development profile that triggers the metadata generation. -->
+    <profile>
+      <id>dev</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>exec-maven-plugin</artifactId>
+            <version>1.2</version>
+            <executions>
+              <execution>
+                <id>build-metadata</id>
+                <phase>generate-sources</phase>
+                <goals>
+                  <goal>exec</goal>
+                </goals>
+                <configuration>
+                  <executable>java</executable>
+                  <arguments>
+                    <argument>-jar</argument>
+                    <argument>../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar</argument>
+                    <argument>BuildMetadataProtoFromXml</argument>
+                    <argument>../resources/PhoneNumberMetaData.xml</argument>
+                    <argument>src</argument>
+                    <argument>false</argument> <!-- Not for testing. -->
+                    <argument>false</argument> <!-- No lite metadata. -->
+                  </arguments>
+                </configuration>
+              </execution>
+              <execution>
+                <id>build-test-metadata</id>
+                <phase>generate-test-sources</phase>
+                <goals>
+                  <goal>exec</goal>
+                </goals>
+                <configuration>
+                  <executable>java</executable>
+                  <arguments>
+                    <argument>-jar</argument>
+                    <argument>../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar</argument>
+                    <argument>BuildMetadataProtoFromXml</argument>
+                    <argument>../resources/PhoneNumberMetaDataForTesting.xml</argument>
+                    <argument>test</argument>
+                    <argument>true</argument> <!-- For testing. -->
+                    <argument>false</argument> <!-- No lite metadata. -->
+                  </arguments>
+                </configuration>
+              </execution>
+              <execution>
+                <id>build-geo-data</id>
+                <phase>generate-sources</phase>
+                <goals>
+                  <goal>exec</goal>
+                </goals>
+                <configuration>
+                  <executable>java</executable>
+                  <arguments>
+                    <argument>-jar</argument>
+                    <argument>../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar</argument>
+                    <argument>GenerateAreaCodeData</argument>
+                    <argument>../resources/geocoding</argument>
+                    <argument>src/com/google/i18n/phonenumbers/geocoding/data</argument>
+                    <argument>false</argument> <!-- Not for testing. -->
+                  </arguments>
+                </configuration>
+              </execution>
+              <execution>
+                <id>build-geo-test-data</id>
+                <phase>generate-test-sources</phase>
+                <goals>
+                  <goal>exec</goal>
+                </goals>
+                <configuration>
+                  <executable>java</executable>
+                  <arguments>
+                    <argument>-jar</argument>
+                    <argument>../tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar</argument>
+                    <argument>GenerateAreaCodeData</argument>
+                    <argument>../resources/test/geocoding</argument>
+                    <argument>test/com/google/i18n/phonenumbers/geocoding/testing_data</argument>
+                    <argument>true</argument> <!-- For testing. -->
+                  </arguments>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
   </profiles>
 
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-  </properties>
-
   <dependencies>
     <dependency>
       <groupId>junit</groupId>
diff --git a/java/release_notes.txt b/java/release_notes.txt
index 1963e56..14e5736 100644
--- a/java/release_notes.txt
+++ b/java/release_notes.txt
@@ -1,3 +1,26 @@
+June 14th, 2011
+* Code changes
+ - Added PhoneNumberOfflineGeocoder, supporting classes and their unittests.
+ - Added GenerateAreaCodeData to transform phone number area mapping files from text files to binary
+   files.
+ - Modified PhoneNumberParserServlet.java and phonenumberparser.jsp to incorporate
+   PhoneNumberOfflineGeocoding in the appengine demo.
+
+* Metadata changes
+ - Added phone number area mapping files for NANPA countries and GB in English, NL in Dutch, AR, CL
+   and ES in Spanish, AT and DE in German, SE in Swedish, BR in Portuguese, KR in English, Korean,
+   Simplified and Traditional Chinese, and CN in Simplified Chinese.
+
+June 10th, 2011
+* Code changes:
+ - Fixes for PhoneNumberMatcher to be more restrictive in valid mode and not match numbers
+   surrounded by Latin characters. This ensures, for example, the string abc123456789acg will not be
+   marked as a phone numbers.
+ - Enable PhoneNumberUtil to handle all digits, rather than a subset
+ - Fix for AYTF issue36 and improvement for US AYTF behaviour.
+* Metadata changes:
+ - Updates: BG, EG, ES, GH, PF, SC, SY, VA
+
 May 24th, 2011
 * Code changes:
  - Phonenumber now implements Serializable.
diff --git a/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java b/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java
deleted file mode 100644
index a73ffee..0000000
--- a/java/resources/com/google/i18n/phonenumbers/BuildMetadataFromXml.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.i18n.phonenumbers;
-
-import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat;
-import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
-import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
-import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * Library to build phone number metadata from the XML format.
- *
- * @author Shaopeng Jia
- */
-public class BuildMetadataFromXml {
-  private static final Logger LOGGER = Logger.getLogger(BuildMetadataFromXml.class.getName());
-  private static Boolean liteBuild;
-
-  // Build the PhoneMetadataCollection from the input XML file.
-  public static PhoneMetadataCollection buildPhoneMetadataCollection(String inputXmlFile,
-      boolean liteBuild) throws Exception {
-    BuildMetadataFromXml.liteBuild = liteBuild;
-    DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
-    DocumentBuilder builder = builderFactory.newDocumentBuilder();
-    File xmlFile = new File(inputXmlFile);
-    Document document = builder.parse(xmlFile);
-    document.getDocumentElement().normalize();
-    Element rootElement = document.getDocumentElement();
-    NodeList territory = rootElement.getElementsByTagName("territory");
-    PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection();
-    int numOfTerritories = territory.getLength();
-    for (int i = 0; i < numOfTerritories; i++) {
-      Element territoryElement = (Element) territory.item(i);
-      String regionCode = territoryElement.getAttribute("id");
-      PhoneMetadata metadata = loadCountryMetadata(regionCode, territoryElement);
-      metadataCollection.addMetadata(metadata);
-    }
-    return metadataCollection;
-  }
-
-  // Build a mapping from a country calling code to the region codes which denote the country/region
-  // represented by that country code. In the case of multiple countries sharing a calling code,
-  // such as the NANPA countries, the one indicated with "isMainCountryForCode" in the metadata
-  // should be first.
-  public static Map<Integer, List<String>> buildCountryCodeToRegionCodeMap(
-      PhoneMetadataCollection metadataCollection) {
-    Map<Integer, List<String>> countryCodeToRegionCodeMap =
-        new TreeMap<Integer, List<String>>();
-    for (PhoneMetadata metadata : metadataCollection.getMetadataList()) {
-      String regionCode = metadata.getId();
-      int countryCode = metadata.getCountryCode();
-      if (countryCodeToRegionCodeMap.containsKey(countryCode)) {
-        if (metadata.isMainCountryForCode()) {
-          countryCodeToRegionCodeMap.get(countryCode).add(0, regionCode);
-        } else {
-          countryCodeToRegionCodeMap.get(countryCode).add(regionCode);
-        }
-      } else {
-        // For most countries, there will be only one region code for the country calling code.
-        List<String> listWithRegionCode = new ArrayList<String>(1);
-        listWithRegionCode.add(regionCode);
-        countryCodeToRegionCodeMap.put(countryCode, listWithRegionCode);
-      }
-    }
-    return countryCodeToRegionCodeMap;
-  }
-
-  private static String validateRE(String regex) {
-    return validateRE(regex, false);
-  }
-
-  private static String validateRE(String regex, boolean removeWhitespace) {
-    // Removes all the whitespace and newline from the regexp. Not using pattern compile options to
-    // make it work across programming languages.
-    if (removeWhitespace) {
-      regex = regex.replaceAll("\\s", "");
-    }
-    Pattern.compile(regex);
-    // return regex itself if it is of correct regex syntax
-    // i.e. compile did not fail with a PatternSyntaxException.
-    return regex;
-  }
-
-  private static PhoneMetadata loadCountryMetadata(String regionCode, Element element) {
-    PhoneMetadata metadata = new PhoneMetadata();
-    metadata.setId(regionCode);
-    metadata.setCountryCode(Integer.parseInt(element.getAttribute("countryCode")));
-    if (element.hasAttribute("leadingDigits")) {
-      metadata.setLeadingDigits(validateRE(element.getAttribute("leadingDigits")));
-    }
-    metadata.setInternationalPrefix(validateRE(element.getAttribute("internationalPrefix")));
-    if (element.hasAttribute("preferredInternationalPrefix")) {
-      String preferredInternationalPrefix = element.getAttribute("preferredInternationalPrefix");
-      metadata.setPreferredInternationalPrefix(preferredInternationalPrefix);
-    }
-    if (element.hasAttribute("nationalPrefixForParsing")) {
-      metadata.setNationalPrefixForParsing(
-          validateRE(element.getAttribute("nationalPrefixForParsing")));
-      if (element.hasAttribute("nationalPrefixTransformRule")) {
-        metadata.setNationalPrefixTransformRule(
-            validateRE(element.getAttribute("nationalPrefixTransformRule")));
-      }
-    }
-    String nationalPrefix = "";
-    String nationalPrefixFormattingRule = "";
-    if (element.hasAttribute("nationalPrefix")) {
-      nationalPrefix = element.getAttribute("nationalPrefix");
-      metadata.setNationalPrefix(nationalPrefix);
-      nationalPrefixFormattingRule =
-          getNationalPrefixFormattingRuleFromElement(element, nationalPrefix);
-
-      if (!metadata.hasNationalPrefixForParsing()) {
-        metadata.setNationalPrefixForParsing(nationalPrefix);
-      }
-    }
-    String carrierCodeFormattingRule = "";
-    if (element.hasAttribute("carrierCodeFormattingRule")) {
-      carrierCodeFormattingRule = validateRE(
-          getDomesticCarrierCodeFormattingRuleFromElement(element, nationalPrefix));
-    }
-    if (element.hasAttribute("preferredExtnPrefix")) {
-      metadata.setPreferredExtnPrefix(element.getAttribute("preferredExtnPrefix"));
-    }
-    if (element.hasAttribute("mainCountryForCode")) {
-      metadata.setMainCountryForCode(true);
-    }
-    if (element.hasAttribute("leadingZeroPossible")) {
-      metadata.setLeadingZeroPossible(true);
-    }
-
-    // Extract availableFormats
-    NodeList numberFormatElements = element.getElementsByTagName("numberFormat");
-    boolean hasExplicitIntlFormatDefined = false;
-
-    int numOfFormatElements = numberFormatElements.getLength();
-    if (numOfFormatElements > 0) {
-      for (int i = 0; i < numOfFormatElements; i++) {
-        Element numberFormatElement = (Element) numberFormatElements.item(i);
-        NumberFormat format = new NumberFormat();
-
-        if (numberFormatElement.hasAttribute("nationalPrefixFormattingRule")) {
-          format.setNationalPrefixFormattingRule(
-              getNationalPrefixFormattingRuleFromElement(numberFormatElement, nationalPrefix));
-        } else {
-          format.setNationalPrefixFormattingRule(nationalPrefixFormattingRule);
-        }
-        if (numberFormatElement.hasAttribute("carrierCodeFormattingRule")) {
-          format.setDomesticCarrierCodeFormattingRule(validateRE(
-              getDomesticCarrierCodeFormattingRuleFromElement(numberFormatElement,
-                                                              nationalPrefix)));
-        } else {
-          format.setDomesticCarrierCodeFormattingRule(carrierCodeFormattingRule);
-        }
-
-        // Extract the pattern for the national format.
-        setLeadingDigitsPatterns(numberFormatElement, format);
-        format.setPattern(validateRE(numberFormatElement.getAttribute("pattern")));
-
-        NodeList formatPattern = numberFormatElement.getElementsByTagName("format");
-        if (formatPattern.getLength() != 1) {
-          LOGGER.log(Level.SEVERE,
-                     "Only one format pattern for a numberFormat element should be defined.");
-          throw new RuntimeException("Invalid number of format patterns for country: " +
-                                     regionCode);
-        }
-        String nationalFormat = formatPattern.item(0).getFirstChild().getNodeValue();
-        format.setFormat(nationalFormat);
-        metadata.addNumberFormat(format);
-
-        // Extract the pattern for international format. If there is no intlFormat, default to
-        // using the national format. If the intlFormat is set to "NA" the intlFormat should be
-        // ignored.
-        NumberFormat intlFormat = new NumberFormat();
-        setLeadingDigitsPatterns(numberFormatElement, intlFormat);
-        intlFormat.setPattern(numberFormatElement.getAttribute("pattern"));
-        NodeList intlFormatPattern = numberFormatElement.getElementsByTagName("intlFormat");
-
-        if (intlFormatPattern.getLength() > 1) {
-          LOGGER.log(Level.SEVERE,
-                     "A maximum of one intlFormat pattern for a numberFormat element should be " +
-                     "defined.");
-          throw new RuntimeException("Invalid number of intlFormat patterns for country: " +
-                                     regionCode);
-        } else if (intlFormatPattern.getLength() == 0) {
-          // Default to use the same as the national pattern if none is defined.
-          intlFormat.setFormat(nationalFormat);
-        } else {
-           String intlFormatPatternValue =
-               intlFormatPattern.item(0).getFirstChild().getNodeValue();
-           if (!intlFormatPatternValue.equals("NA")) {
-             intlFormat.setFormat(intlFormatPatternValue);
-           }
-           hasExplicitIntlFormatDefined = true;
-        }
-
-        if (intlFormat.hasFormat()) {
-          metadata.addIntlNumberFormat(intlFormat);
-        }
-      }
-      // Only a small number of regions need to specify the intlFormats in the xml. For the majority
-      // of countries the intlNumberFormat metadata is an exact copy of the national NumberFormat
-      // metadata. To minimize the size of the metadata file, we only keep intlNumberFormats that
-      // actually differ in some way to the national formats.
-      if (!hasExplicitIntlFormatDefined) {
-        metadata.clearIntlNumberFormat();
-      }
-    }
-
-    PhoneNumberDesc generalDesc = new PhoneNumberDesc();
-    generalDesc = processPhoneNumberDescElement(generalDesc, element, "generalDesc");
-    metadata.setGeneralDesc(generalDesc);
-    metadata.setFixedLine(processPhoneNumberDescElement(generalDesc, element, "fixedLine"));
-    metadata.setMobile(processPhoneNumberDescElement(generalDesc, element, "mobile"));
-    metadata.setTollFree(processPhoneNumberDescElement(generalDesc, element, "tollFree"));
-    metadata.setPremiumRate(processPhoneNumberDescElement(generalDesc, element, "premiumRate"));
-    metadata.setSharedCost(processPhoneNumberDescElement(generalDesc, element, "sharedCost"));
-    metadata.setVoip(processPhoneNumberDescElement(generalDesc, element, "voip"));
-    metadata.setPersonalNumber(processPhoneNumberDescElement(generalDesc, element,
-                                                             "personalNumber"));
-    metadata.setPager(processPhoneNumberDescElement(generalDesc, element, "pager"));
-    metadata.setUan(processPhoneNumberDescElement(generalDesc, element, "uan"));
-    metadata.setNoInternationalDialling(processPhoneNumberDescElement(generalDesc, element,
-                                                                      "noInternationalDialling"));
-
-    if (metadata.getMobile().getNationalNumberPattern().equals(
-        metadata.getFixedLine().getNationalNumberPattern())) {
-      metadata.setSameMobileAndFixedLinePattern(true);
-    }
-    return metadata;
-  }
-
-  private static void setLeadingDigitsPatterns(Element numberFormatElement, NumberFormat format) {
-    NodeList leadingDigitsPatternNodes = numberFormatElement.getElementsByTagName("leadingDigits");
-    int numOfLeadingDigitsPatterns = leadingDigitsPatternNodes.getLength();
-    if (numOfLeadingDigitsPatterns > 0) {
-      for (int i = 0; i < numOfLeadingDigitsPatterns; i++) {
-        format.addLeadingDigitsPattern(
-            validateRE((leadingDigitsPatternNodes.item(i)).getFirstChild().getNodeValue(), true));
-      }
-    }
-  }
-
-  private static String getNationalPrefixFormattingRuleFromElement(Element element,
-                                                                   String nationalPrefix) {
-    String nationalPrefixFormattingRule = element.getAttribute("nationalPrefixFormattingRule");
-    // Replace $NP with national prefix and $FG with the first group ($1).
-    nationalPrefixFormattingRule =
-        nationalPrefixFormattingRule.replaceFirst("\\$NP", nationalPrefix)
-            .replaceFirst("\\$FG", "\\$1");
-    return nationalPrefixFormattingRule;
-  }
-
-  private static String getDomesticCarrierCodeFormattingRuleFromElement(Element element,
-                                                                        String nationalPrefix) {
-    String carrierCodeFormattingRule = element.getAttribute("carrierCodeFormattingRule");
-    // Replace $FG with the first group ($1) and $NP with the national prefix.
-    carrierCodeFormattingRule = carrierCodeFormattingRule.replaceFirst("\\$FG", "\\$1")
-        .replaceFirst("\\$NP", nationalPrefix);
-    return carrierCodeFormattingRule;
-  }
-
-  /**
-   * Processes a phone number description element from the XML file and returns it as a
-   * PhoneNumberDesc. If the description element is a fixed line or mobile number, the general
-   * description will be used to fill in the whole element if necessary, or any components that are
-   * missing. For all other types, the general description will only be used to fill in missing
-   * components if the type has a partial definition. For example, if no "tollFree" element exists,
-   * we assume there are no toll free numbers for that locale, and return a phone number description
-   * with "NA" for both the national and possible number patterns.
-   *
-   * @param generalDesc  a generic phone number description that will be used to fill in missing
-   *                     parts of the description
-   * @param countryElement  the XML element representing all the country information
-   * @param numberType  the name of the number type, corresponding to the appropriate tag in the XML
-   *                    file with information about that type
-   * @return  complete description of that phone number type
-   */
-  private static PhoneNumberDesc processPhoneNumberDescElement(PhoneNumberDesc generalDesc,
-                                                               Element countryElement,
-                                                               String numberType) {
-    NodeList phoneNumberDescList = countryElement.getElementsByTagName(numberType);
-    PhoneNumberDesc numberDesc = new PhoneNumberDesc();
-    if (phoneNumberDescList.getLength() == 0 &&
-        (!numberType.equals("fixedLine") && !numberType.equals("mobile") &&
-         !numberType.equals("generalDesc"))) {
-      numberDesc.setNationalNumberPattern("NA");
-      numberDesc.setPossibleNumberPattern("NA");
-      return numberDesc;
-    }
-    numberDesc.mergeFrom(generalDesc);
-    if (phoneNumberDescList.getLength() > 0) {
-      Element element = (Element) phoneNumberDescList.item(0);
-      NodeList possiblePattern = element.getElementsByTagName("possibleNumberPattern");
-      if (possiblePattern.getLength() > 0) {
-        numberDesc.setPossibleNumberPattern(
-            validateRE(possiblePattern.item(0).getFirstChild().getNodeValue(), true));
-      }
-
-      NodeList validPattern = element.getElementsByTagName("nationalNumberPattern");
-      if (validPattern.getLength() > 0) {
-        numberDesc.setNationalNumberPattern(
-            validateRE(validPattern.item(0).getFirstChild().getNodeValue(), true));
-      }
-
-      if (!liteBuild) {
-        NodeList exampleNumber = element.getElementsByTagName("exampleNumber");
-        if (exampleNumber.getLength() > 0) {
-          numberDesc.setExampleNumber(exampleNumber.item(0).getFirstChild().getNodeValue());
-        }
-      }
-    }
-    return numberDesc;
-  }
-}
diff --git a/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java b/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java
deleted file mode 100644
index 2246606..0000000
--- a/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.i18n.phonenumbers;
-
-import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
-import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
-
-import java.io.BufferedWriter;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.util.Formatter;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Tool to convert phone number metadata from the XML format to protocol buffer format.
- *
- * @author Shaopeng Jia
- */
-public class BuildMetadataProtoFromXml {
-  private static final String PACKAGE_NAME = PhoneNumberUtil.class.getPackage().getName();
-  private static final String TEST_COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME =
-      "CountryCodeToRegionCodeMapForTesting";
-  private static final String COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME =
-      "CountryCodeToRegionCodeMap";
-
-  private static final String HELP_MESSAGE =
-      "Usage:\n" +
-      "BuildMetadataProtoFromXml <inputFile> <outputDir> <forTesting> [<liteBuild>]\n" +
-      "\n" +
-      "where:\n" +
-      "  inputFile    The input file containing phone number metadata in XML format.\n" +
-      "  outputDir    The output directory to store phone number metadata in proto\n" +
-      "               format (one file per region) and the country code to region code\n" +
-      "               mapping file.\n" +
-      "  forTesting   Flag whether to generate metadata for testing purposes or not.\n" +
-      "  liteBuild    Whether to generate the lite-version of the metadata (default:\n" +
-      "               false). When set to true certain metadata will be omitted.\n" +
-      "               At this moment, example numbers information is omitted.\n" +
-      "\n" +
-      "Metadata will be stored in:\n" +
-      "  <outputDir>" + PhoneNumberUtil.META_DATA_FILE_PREFIX + "_*\n" +
-      "Mapping file will be stored in:\n" +
-      "  <outputDir>/" + PACKAGE_NAME.replaceAll("\\.", "/") + "/" +
-          COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME + ".java\n" +
-      "\n" +
-      "Example command line invocation:\n" +
-      "BuildMetadataProtoFromXml PhoneNumberMetadata.xml src false false\n";
-
-  public static void main(String[] args) throws Exception {
-    if (args.length != 3 && args.length != 4) {
-      System.err.println(HELP_MESSAGE);
-      System.exit(1);
-    }
-    String inputFile = args[0];
-    String outputDir = args[1];
-    boolean forTesting = args[2].equals("true");
-    boolean liteBuild = args.length > 3 && args[3].equals("true");
-
-    String filePrefix;
-    if (forTesting) {
-      filePrefix = outputDir + PhoneNumberUtilTest.TEST_META_DATA_FILE_PREFIX;
-    } else {
-      filePrefix = outputDir + PhoneNumberUtil.META_DATA_FILE_PREFIX;
-    }
-
-    PhoneMetadataCollection metadataCollection =
-        BuildMetadataFromXml.buildPhoneMetadataCollection(inputFile, liteBuild);
-
-    for (PhoneMetadata metadata : metadataCollection.getMetadataList()) {
-      String regionCode = metadata.getId();
-      PhoneMetadataCollection outMetadataCollection = new PhoneMetadataCollection();
-      outMetadataCollection.addMetadata(metadata);
-      FileOutputStream outputForRegion = new FileOutputStream(filePrefix + "_" + regionCode);
-      ObjectOutputStream out = new ObjectOutputStream(outputForRegion);
-      outMetadataCollection.writeExternal(out);
-      out.close();
-    }
-
-    Map<Integer, List<String>> countryCodeToRegionCodeMap =
-        BuildMetadataFromXml.buildCountryCodeToRegionCodeMap(metadataCollection);
-
-    writeCountryCallingCodeMappingToJavaFile(countryCodeToRegionCodeMap, outputDir, forTesting);
-  }
-
-  static final String COPYRIGHT_NOTICE =
-      "/*\n" +
-      " * Copyright (C) 2010 Google Inc.\n" +
-      " *\n" +
-      " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
-      " * you may not use this file except in compliance with the License.\n" +
-      " * You may obtain a copy of the License at\n" +
-      " *\n" +
-      " * http://www.apache.org/licenses/LICENSE-2.0\n" +
-      " *\n" +
-      " * Unless required by applicable law or agreed to in writing, software\n" +
-      " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
-      " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
-      " * See the License for the specific language governing permissions and\n" +
-      " * limitations under the License.\n" +
-      " *\n" +
-      " * This file is automatically generated by BuildMetadataProtoFromXml. Please\n" +
-      " * don't modify directly.\n" +
-      " */\n\n";
-  private static final String MAPPING_IMPORTS =
-      "import java.util.ArrayList;\n" +
-      "import java.util.HashMap;\n" +
-      "import java.util.List;\n" +
-      "import java.util.Map;\n";
-  private static final String MAPPING_COMMENT =
-      "  // A mapping from a country code to the region codes which denote the\n" +
-      "  // country/region represented by that country code. In the case of multiple\n" +
-      "  // countries sharing a calling code, such as the NANPA countries, the one\n" +
-      "  // indicated with \"isMainCountryForCode\" in the metadata should be first.\n";
-  private static final double MAPPING_LOAD_FACTOR = 0.75;
-  private static final String MAPPING_COMMENT_2 =
-      "    // The capacity is set to %d as there are %d different country codes,\n" +
-      "    // and this offers a load factor of roughly " + MAPPING_LOAD_FACTOR + ".\n";
-
-  private static void writeCountryCallingCodeMappingToJavaFile(
-      Map<Integer, List<String>> countryCodeToRegionCodeMap,
-      String outputDir, boolean forTesting) throws IOException {
-    String mappingClassName;
-    if (forTesting) {
-      mappingClassName = TEST_COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME;
-    } else {
-      mappingClassName = COUNTRY_CODE_TO_REGION_CODE_MAP_CLASS_NAME;
-    }
-    String mappingFile =
-        outputDir + "/" + PACKAGE_NAME.replaceAll("\\.", "/") + "/" + mappingClassName + ".java";
-    int capacity = (int) (countryCodeToRegionCodeMap.size() / MAPPING_LOAD_FACTOR);
-
-    BufferedWriter writer = new BufferedWriter(new FileWriter(mappingFile));
-
-    writer.write(COPYRIGHT_NOTICE);
-    if (PACKAGE_NAME.length() > 0) {
-      writer.write("package " + PACKAGE_NAME + ";\n\n");
-    }
-    writer.write(MAPPING_IMPORTS);
-    writer.write("\n");
-    writer.write("public class " + mappingClassName + " {\n");
-    writer.write(MAPPING_COMMENT);
-    writer.write("  static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {\n");
-    Formatter formatter = new Formatter(writer);
-    formatter.format(MAPPING_COMMENT_2, capacity, countryCodeToRegionCodeMap.size());
-    writer.write("    Map<Integer, List<String>> countryCodeToRegionCodeMap =\n");
-    writer.write("        new HashMap<Integer, List<String>>(" + capacity + ");\n");
-    writer.write("\n");
-    writer.write("    ArrayList<String> listWithRegionCode;\n");
-    writer.write("\n");
-
-    for (Map.Entry<Integer, List<String>> entry : countryCodeToRegionCodeMap.entrySet()) {
-      int countryCallingCode = entry.getKey();
-      List<String> regionCodes = entry.getValue();
-      writer.write("    listWithRegionCode = new ArrayList<String>(" + regionCodes.size() + ");\n");
-      for (String regionCode : regionCodes) {
-        writer.write("    listWithRegionCode.add(\"" + regionCode + "\");\n");
-      }
-      writer.write("    countryCodeToRegionCodeMap.put(" + countryCallingCode +
-                   ", listWithRegionCode);\n");
-      writer.write("\n");
-    }
-
-    writer.write("    return countryCodeToRegionCodeMap;\n");
-    writer.write("  }\n");
-    writer.write("}\n");
-
-    writer.flush();
-    writer.close();
-  }
-}
diff --git a/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java b/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
index 806ecc9..1728eee 100644
--- a/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
+++ b/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java
@@ -124,7 +124,9 @@
   private boolean maybeCreateNewTemplate() {
     // When there are multiple available formats, the formatter uses the first format where a
     // formatting template could be created.
-    for (NumberFormat numberFormat : possibleFormats) {
+    Iterator<NumberFormat> it = possibleFormats.iterator();
+    while (it.hasNext()) {
+      NumberFormat numberFormat = it.next();
       String pattern = numberFormat.getPattern();
       if (currentFormattingPattern.equals(pattern)) {
         return false;
@@ -132,6 +134,8 @@
       if (createFormattingTemplate(numberFormat)) {
         currentFormattingPattern = pattern;
         return true;
+      } else {  // Remove the current number format from possibleFormats.
+        it.remove();
       }
     }
     ableToFormat = false;
@@ -189,7 +193,7 @@
     numberPattern = STANDALONE_DIGIT_PATTERN.matcher(numberPattern).replaceAll("\\\\d");
     formattingTemplate.setLength(0);
     String tempTemplate = getFormattingTemplate(numberPattern, format.getFormat());
-    if (tempTemplate.length() > nationalNumber.length()) {
+    if (tempTemplate.length() > 0) {
       formattingTemplate.append(tempTemplate);
       return true;
     }
@@ -205,6 +209,11 @@
     Matcher m = regexCache.getPatternForRegex(numberPattern).matcher(longestPhoneNumber);
     m.find();  // this will always succeed
     String aPhoneNumber = m.group();
+    // No formatting template can be created if the number of digits entered so far is longer than
+    // the maximum the current formatting rule can accommodate.
+    if (aPhoneNumber.length() < nationalNumber.length()) {
+      return "";
+    }
     // Formats the number according to numberFormat
     String template = aPhoneNumber.replaceAll(numberPattern, numberFormat);
     // Replaces each digit with character digitPlaceholder
@@ -471,17 +480,20 @@
   // version, if nextChar is a digit in non-ASCII format. This method assumes its input is either a
   // digit or the plus sign.
   private char normalizeAndAccrueDigitsAndPlusSign(char nextChar, boolean rememberPosition) {
+    char normalizedChar;
     if (nextChar == PhoneNumberUtil.PLUS_SIGN) {
+      normalizedChar = nextChar;
       accruedInputWithoutFormatting.append(nextChar);
     } else {
-      nextChar = PhoneNumberUtil.DIGIT_MAPPINGS.get(nextChar);
-      accruedInputWithoutFormatting.append(nextChar);
-      nationalNumber.append(nextChar);
+      int radix = 10;
+      normalizedChar = Character.forDigit(Character.digit(nextChar, radix), radix);
+      accruedInputWithoutFormatting.append(normalizedChar);
+      nationalNumber.append(normalizedChar);
     }
     if (rememberPosition) {
       positionToRemember = accruedInputWithoutFormatting.length();
     }
-    return nextChar;
+    return normalizedChar;
   }
 
   private String inputDigitHelper(char nextChar) {
diff --git a/java/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java b/java/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
index e67217c..07fd3e9 100644
--- a/java/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
+++ b/java/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2011 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -12,9 +12,10 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
- * This file is automatically generated by BuildMetadataProtoFromXml. Please
- * don't modify directly.
+ */
+
+/* This file is automatically generated by {@link BuildMetadataProtoFromXml}.
+ * Please don't modify it directly.
  */
 
 package com.google.i18n.phonenumbers;
diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java
index b00a0e1..e994959 100644
--- a/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java
+++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatch.java
@@ -57,7 +57,7 @@
   /** The start index into the text. */
   private final int start;
   /** The raw substring matched. */
-  private final String match;
+  private final String rawString;
   /** The matched phone number. */
   private final PhoneNumber number;
 
@@ -65,18 +65,18 @@
    * Creates a new match.
    *
    * @param start  the start index into the target text
-   * @param match  the matched substring of the target text
+   * @param rawString  the matched substring of the target text
    * @param number  the matched phone number
    */
-  PhoneNumberMatch(int start, String match, PhoneNumber number) {
+  PhoneNumberMatch(int start, String rawString, PhoneNumber number) {
     if (start < 0) {
       throw new IllegalArgumentException("Start index must be >= 0.");
     }
-    if (match == null || number == null) {
+    if (rawString == null || number == null) {
       throw new NullPointerException();
     }
     this.start = start;
-    this.match = match;
+    this.rawString = rawString;
     this.number = number;
   }
 
@@ -92,17 +92,17 @@
 
   /** Returns the exclusive end index of the matched phone number within the searched text. */
   public int end() {
-    return start + match.length();
+    return start + rawString.length();
   }
 
   /** Returns the raw string matched as a phone number in the searched text. */
   public String rawString() {
-    return match;
+    return rawString;
   }
 
   @Override
   public int hashCode() {
-    return Arrays.hashCode(new Object[]{start, match, number});
+    return Arrays.hashCode(new Object[]{start, rawString, number});
   }
 
   @Override
@@ -114,11 +114,12 @@
       return false;
     }
     PhoneNumberMatch other = (PhoneNumberMatch) obj;
-    return match.equals(other.match) && (start == other.start) && number.equals(other.number);
+    return rawString.equals(other.rawString) && (start == other.start) &&
+        number.equals(other.number);
   }
 
   @Override
   public String toString() {
-    return "PhoneNumberMatch [" + start() + "," + end() + ") " + match;
+    return "PhoneNumberMatch [" + start() + "," + end() + ") " + rawString;
   }
 }
diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java
index 68ad3ad..c8a7651 100644
--- a/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java
+++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java
@@ -19,6 +19,7 @@
 import com.google.i18n.phonenumbers.PhoneNumberUtil.Leniency;
 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
 
+import java.lang.Character.UnicodeBlock;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.regex.Matcher;
@@ -82,6 +83,11 @@
    */
   private static final Pattern GROUP_SEPARATOR = Pattern.compile("\\p{Z}+");
 
+  /**
+   * Punctuation that may be at the start of a phone number - brackets and plus signs.
+   */
+  private static final Pattern LEAD_CLASS;
+
   static {
     /* Builds the MATCHING_BRACKETS and PATTERN regular expressions. The building blocks below exist
      * to make the pattern more easily understood. */
@@ -112,7 +118,7 @@
      * country code. */
     int digitBlockLimit =
         PhoneNumberUtil.MAX_LENGTH_FOR_NSN + PhoneNumberUtil.MAX_LENGTH_COUNTRY_CODE;
-    /* Limit on the number of blocks separated by punctuation. Use digitBlockLimit since in some
+    /* Limit on the number of blocks separated by punctuation. Uses digitBlockLimit since some
      * formats use spaces to separate each digit. */
     String blockLimit = limit(0, digitBlockLimit);
 
@@ -120,8 +126,9 @@
     String punctuation = "[" + PhoneNumberUtil.VALID_PUNCTUATION + "]" + punctuationLimit;
     /* A digits block without punctuation. */
     String digitSequence = "\\p{Nd}" + limit(1, digitBlockLimit);
-    /* Punctuation that may be at the start of a phone number - brackets and plus signs. */
+
     String leadClass = "[" + openingParens + PhoneNumberUtil.PLUS_CHARS + "]";
+    LEAD_CLASS = Pattern.compile(leadClass);
 
     /* Phone number pattern allowing optional punctuation. */
     PATTERN = Pattern.compile(
@@ -145,7 +152,7 @@
   }
 
   /** The phone number utility. */
-  private final PhoneNumberUtil util;
+  private final PhoneNumberUtil phoneUtil;
   /** The text searched for phone numbers. */
   private final CharSequence text;
   /**
@@ -189,7 +196,7 @@
     if (maxTries < 0) {
       throw new IllegalArgumentException();
     }
-    this.util = util;
+    this.phoneUtil = util;
     this.text = (text != null) ? text : "";
     this.preferredRegion = country;
     this.leniency = leniency;
@@ -265,6 +272,25 @@
   }
 
   /**
+   * Helper method to determine if a character is a Latin-script letter or not. For our purposes,
+   * combining marks should also return true since we assume they have been added to a preceding
+   * Latin character.
+   */
+  static boolean isLatinLetter(char letter) {
+    // Combining marks are a subset of non-spacing-mark.
+    if (!Character.isLetter(letter) && Character.getType(letter) != Character.NON_SPACING_MARK) {
+      return false;
+    }
+    UnicodeBlock block = UnicodeBlock.of(letter);
+    return block.equals(UnicodeBlock.BASIC_LATIN) ||
+        block.equals(UnicodeBlock.LATIN_1_SUPPLEMENT) ||
+        block.equals(UnicodeBlock.LATIN_EXTENDED_A) ||
+        block.equals(UnicodeBlock.LATIN_EXTENDED_ADDITIONAL) ||
+        block.equals(UnicodeBlock.LATIN_EXTENDED_B) ||
+        block.equals(UnicodeBlock.COMBINING_DIACRITICAL_MARKS);
+  }
+
+  /**
    * Attempts to extract a match from a {@code candidate} character sequence.
    *
    * @param candidate  the candidate text that might contain a phone number
@@ -277,6 +303,21 @@
       return null;
     }
 
+    // If leniency is set to VALID only, we also want to skip numbers that are surrounded by Latin
+    // alphabetic characters, to skip cases like abc8005001234 or 8005001234def.
+    if (leniency == Leniency.VALID) {
+      // If the candidate is not at the start of the text, and does not start with punctuation and
+      // the previous character is not a Latin letter, return null.
+      if (offset > 0 &&
+          (!LEAD_CLASS.matcher(candidate).lookingAt() && isLatinLetter(text.charAt(offset - 1)))) {
+        return null;
+      }
+      int lastCharIndex = offset + candidate.length();
+      if (lastCharIndex < text.length() && isLatinLetter(text.charAt(lastCharIndex))) {
+        return null;
+      }
+    }
+
     // Try to come up with a valid match given the entire candidate.
     String rawString = candidate.toString();
     PhoneNumberMatch match = parseAndVerify(rawString, offset);
@@ -299,7 +340,7 @@
    */
   private PhoneNumberMatch extractInnerMatch(String candidate, int offset) {
     // Try removing either the first or last "group" in the number and see if this gives a result.
-    // We consider white space to be a possible indications of the start or end of the phone number.
+    // We consider white space to be a possible indication of the start or end of the phone number.
     Matcher groupMatcher = GROUP_SEPARATOR.matcher(candidate);
 
     if (groupMatcher.find()) {
@@ -350,8 +391,8 @@
       if (!MATCHING_BRACKETS.matcher(candidate).matches()) {
         return null;
       }
-      PhoneNumber number = util.parse(candidate, preferredRegion);
-      if (leniency.verify(number, util)) {
+      PhoneNumber number = phoneUtil.parse(candidate, preferredRegion);
+      if (leniency.verify(number, phoneUtil)) {
         return new PhoneNumberMatch(offset, candidate, number);
       }
     } catch (NumberParseException e) {
diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
index b6fd79e..ac8e103 100644
--- a/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
+++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
@@ -47,6 +47,10 @@
  * <p>If you use this library, and want to be notified about important changes, please sign up to
  * our <a href="http://groups.google.com/group/libphonenumber-discuss/about">mailing list</a>.
  *
+ * 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
+ *
  * @author Shaopeng Jia
  * @author Lara Rennie
  */
@@ -77,7 +81,7 @@
   // Region-code for the unknown region.
   private static final String UNKNOWN_REGION = "ZZ";
 
-  // The set of regions that share country code 1.
+  // 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);
@@ -88,23 +92,18 @@
 
   private static final String RFC3966_EXTN_PREFIX = ";ext=";
 
-  // These mappings map a character (key) to a specific digit that should replace it for
-  // normalization purposes. Non-European digits that may be used in phone numbers are mapped to a
-  // European equivalent.
-  static final Map<Character, Character> DIGIT_MAPPINGS;
-
   // Only upper-case variants of alpha characters are stored.
   private static final Map<Character, Character> ALPHA_MAPPINGS;
 
   // For performance reasons, amalgamate both into one map.
-  private static final Map<Character, Character> ALL_NORMALIZATION_MAPPINGS;
+  private static final Map<Character, Character> ALPHA_PHONE_MAPPINGS;
 
   // Separate map of all symbols that we wish to retain when formatting alpha numbers. This
   // includes digits, ASCII letters and number grouping symbols such as "-" and " ".
   private static final Map<Character, Character> ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
 
   static {
-    // Simple ASCII digits map used to populate DIGIT_MAPPINGS and
+    // Simple ASCII digits map used to populate ALPHA_PHONE_MAPPINGS and
     // ALL_PLUS_NUMBER_GROUPING_SYMBOLS.
     HashMap<Character, Character> asciiDigitMappings = new HashMap<Character, Character>();
     asciiDigitMappings.put('0', '0');
@@ -118,40 +117,6 @@
     asciiDigitMappings.put('8', '8');
     asciiDigitMappings.put('9', '9');
 
-    HashMap<Character, Character> digitMap = new HashMap<Character, Character>(50);
-    digitMap.putAll(asciiDigitMappings);
-    digitMap.put('\uFF10', '0');  // Fullwidth digit 0
-    digitMap.put('\u0660', '0');  // Arabic-indic digit 0
-    digitMap.put('\u06F0', '0');  // Eastern-Arabic digit 0
-    digitMap.put('\uFF11', '1');  // Fullwidth digit 1
-    digitMap.put('\u0661', '1');  // Arabic-indic digit 1
-    digitMap.put('\u06F1', '1');  // Eastern-Arabic digit 1
-    digitMap.put('\uFF12', '2');  // Fullwidth digit 2
-    digitMap.put('\u0662', '2');  // Arabic-indic digit 2
-    digitMap.put('\u06F2', '2');  // Eastern-Arabic digit 2
-    digitMap.put('\uFF13', '3');  // Fullwidth digit 3
-    digitMap.put('\u0663', '3');  // Arabic-indic digit 3
-    digitMap.put('\u06F3', '3');  // Eastern-Arabic digit 3
-    digitMap.put('\uFF14', '4');  // Fullwidth digit 4
-    digitMap.put('\u0664', '4');  // Arabic-indic digit 4
-    digitMap.put('\u06F4', '4');  // Eastern-Arabic digit 4
-    digitMap.put('\uFF15', '5');  // Fullwidth digit 5
-    digitMap.put('\u0665', '5');  // Arabic-indic digit 5
-    digitMap.put('\u06F5', '5');  // Eastern-Arabic digit 5
-    digitMap.put('\uFF16', '6');  // Fullwidth digit 6
-    digitMap.put('\u0666', '6');  // Arabic-indic digit 6
-    digitMap.put('\u06F6', '6');  // Eastern-Arabic digit 6
-    digitMap.put('\uFF17', '7');  // Fullwidth digit 7
-    digitMap.put('\u0667', '7');  // Arabic-indic digit 7
-    digitMap.put('\u06F7', '7');  // Eastern-Arabic digit 7
-    digitMap.put('\uFF18', '8');  // Fullwidth digit 8
-    digitMap.put('\u0668', '8');  // Arabic-indic digit 8
-    digitMap.put('\u06F8', '8');  // Eastern-Arabic digit 8
-    digitMap.put('\uFF19', '9');  // Fullwidth digit 9
-    digitMap.put('\u0669', '9');  // Arabic-indic digit 9
-    digitMap.put('\u06F9', '9');  // Eastern-Arabic digit 9
-    DIGIT_MAPPINGS = Collections.unmodifiableMap(digitMap);
-
     HashMap<Character, Character> alphaMap = new HashMap<Character, Character>(40);
     alphaMap.put('A', '2');
     alphaMap.put('B', '2');
@@ -182,9 +147,9 @@
     ALPHA_MAPPINGS = Collections.unmodifiableMap(alphaMap);
 
     HashMap<Character, Character> combinedMap = new HashMap<Character, Character>(100);
-    combinedMap.putAll(alphaMap);
-    combinedMap.putAll(digitMap);
-    ALL_NORMALIZATION_MAPPINGS = Collections.unmodifiableMap(combinedMap);
+    combinedMap.putAll(ALPHA_MAPPINGS);
+    combinedMap.putAll(asciiDigitMappings);
+    ALPHA_PHONE_MAPPINGS = Collections.unmodifiableMap(combinedMap);
 
     HashMap<Character, Character> allPlusNumberGroupings = new HashMap<Character, Character>();
     // Put (lower letter -> upper letter) and (upper letter -> upper letter) mappings.
@@ -226,13 +191,12 @@
   // found as a leading character only.
   // This consists of dash characters, white space characters, full stops, slashes,
   // square brackets, parentheses and tildes. It also includes the letter 'x' as that is found as a
-  // placeholder for carrier information in some phone numbers.
+  // 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";
 
-  // Digits accepted in phone numbers that we understand.
-  private static final String VALID_DIGITS =
-      Arrays.toString(DIGIT_MAPPINGS.keySet().toArray()).replaceAll("[, \\[\\]]", "");
+  private static final String DIGITS = "\\p{Nd}";
   // We accept alpha characters in phone numbers, ASCII only, upper and lower case.
   private static final String VALID_ALPHA =
       Arrays.toString(ALPHA_MAPPINGS.keySet().toArray()).replaceAll("[, \\[\\]]", "") +
@@ -240,8 +204,7 @@
   static final String PLUS_CHARS = "+\uFF0B";
   private static final Pattern PLUS_CHARS_PATTERN = Pattern.compile("[" + PLUS_CHARS + "]+");
   private static final Pattern SEPARATOR_PATTERN = Pattern.compile("[" + VALID_PUNCTUATION + "]+");
-  private static final Pattern CAPTURING_DIGIT_PATTERN =
-      Pattern.compile("([" + VALID_DIGITS + "])");
+  private static final Pattern CAPTURING_DIGIT_PATTERN = Pattern.compile("(" + DIGITS + ")");
 
   // Regular expression of acceptable characters that may start a phone number for the purposes of
   // parsing. This allows us to strip away meaningless prefixes to phone numbers that may be
@@ -249,7 +212,7 @@
   // does not contain alpha characters, although they may be used later in the number. It also does
   // not include other punctuation, as this will be stripped later during parsing and is of no
   // information value when parsing a number.
-  private static final String VALID_START_CHAR = "[" + PLUS_CHARS + VALID_DIGITS + "]";
+  private static final String VALID_START_CHAR = "[" + PLUS_CHARS + DIGITS + "]";
   static final Pattern VALID_START_CHAR_PATTERN = Pattern.compile(VALID_START_CHAR);
 
   // Regular expression of characters typically used to start a second phone number for the purposes
@@ -280,8 +243,8 @@
   // plus_sign*([punctuation]*[digits]){3,}([punctuation]|[digits]|[alpha])*
   // Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
   private static final String VALID_PHONE_NUMBER =
-      "[" + PLUS_CHARS + "]*(?:[" + VALID_PUNCTUATION + "]*[" + VALID_DIGITS + "]){3,}[" +
-      VALID_PUNCTUATION + VALID_ALPHA + VALID_DIGITS + "]*";
+      "[" + PLUS_CHARS + "]*(?:[" + VALID_PUNCTUATION + "]*" + DIGITS + "){3,}[" +
+      VALID_PUNCTUATION + VALID_ALPHA + DIGITS + "]*";
 
   // Default extension prefix to use when formatting. This will be put in front of any extension
   // component of the number, after the main national number is formatted. For example, if you wish
@@ -301,13 +264,13 @@
   // Canonical-equivalence doesn't seem to be an option with Android java, so we allow two options
   // for representing the accented o - the character itself, and one in the unicode decomposed form
   // with the combining acute accent.
-  private static final String CAPTURING_EXTN_DIGITS = "([" + VALID_DIGITS + "]{1,7})";
+  private static final String CAPTURING_EXTN_DIGITS = "(" + DIGITS + "{1,7})";
   static final String KNOWN_EXTN_PATTERNS =
       RFC3966_EXTN_PREFIX + CAPTURING_EXTN_DIGITS + "|" +
       "[ \u00A0\\t,]*(?:ext(?:ensi(?:o\u0301?|\u00F3))?n?|" +
       "\uFF45\uFF58\uFF54\uFF4E?|[,x\uFF58#\uFF03~\uFF5E]|int|anexo|\uFF49\uFF4E\uFF54)" +
       "[:\\.\uFF0E]?[ \u00A0\\t,-]*" + CAPTURING_EXTN_DIGITS + "#?|" +
-      "[- ]+([" + VALID_DIGITS + "]{1,5})#";
+      "[- ]+(" + DIGITS + "{1,5})#";
 
   // Regexp of all known extension prefixes used by different regions followed by 1 or more valid
   // digits, for use when parsing.
@@ -342,7 +305,7 @@
 
   /**
    * INTERNATIONAL and NATIONAL formats are consistent with the definition in ITU-T Recommendation
-   * E. 123. For example, the number of the Google Zurich office will be written as
+   * 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
@@ -527,13 +490,15 @@
   /**
    * Normalizes a string of characters representing a phone number. This performs the following
    * conversions:
-   *   Wide-ascii digits are converted to normal ASCII (European) digits.
+   *   Punctuation is stripped.
+   *   For ALPHA/VANITY numbers:
    *   Letters are converted to their numeric representation on a telephone keypad. The keypad
    *       used here is the one defined in ITU Recommendation E.161. This is only done if there are
-   *       3 or more letters in the number, to lessen the risk that such letters are typos -
-   *       otherwise alpha characters are stripped.
-   *   Punctuation is stripped.
+   *       3 or more letters in the number, to lessen the risk that such letters are typos.
+   *   For other numbers:
+   *   Wide-ascii digits are converted to normal ASCII (European) digits.
    *   Arabic-Indic numerals are converted to European numerals.
+   *   Spurious alpha characters are stripped.
    *
    * @param number  a string of characters representing a phone number
    * @return        the normalized string version of the phone number
@@ -541,9 +506,9 @@
   static String normalize(String number) {
     Matcher m = VALID_ALPHA_PHONE_PATTERN.matcher(number);
     if (m.matches()) {
-      return normalizeHelper(number, ALL_NORMALIZATION_MAPPINGS, true);
+      return normalizeHelper(number, ALPHA_PHONE_MAPPINGS, true);
     } else {
-      return normalizeHelper(number, DIGIT_MAPPINGS, true);
+      return normalizeDigitsOnly(number);
     }
   }
 
@@ -567,16 +532,23 @@
    * @return        the normalized string version of the phone number
    */
   public static String normalizeDigitsOnly(String number) {
-    return normalizeHelper(number, DIGIT_MAPPINGS, true);
+    int numberLength = number.length();
+    StringBuilder normalizedDigits = new StringBuilder(numberLength);
+    for (int i = 0; i < numberLength; i++) {
+      int d = Character.digit(number.charAt(i), 10);
+      if (d != -1) {
+        normalizedDigits.append(d);
+      }
+    }
+    return normalizedDigits.toString();
   }
 
   /**
    * Converts all alpha characters in a number to their respective digits on a keypad, but retains
-   * existing formatting. This Java implementation of this function also converts wide-ascii digits
-   * to normal ascii digits, and converts Arabic-Indic numerals to European numerals.
+   * existing formatting.
    */
   public static String convertAlphaCharactersInNumber(String number) {
-    return normalizeHelper(number, ALL_NORMALIZATION_MAPPINGS, false);
+    return normalizeHelper(number, ALPHA_PHONE_MAPPINGS, false);
   }
 
   /**
@@ -604,15 +576,16 @@
    * </pre>
    *
    * N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
-   * using it for most purposes. Read the following carefully before deciding to use this method:
-   *
-   *  - geographical area codes change over time, and this method honors those changes; therefore,
-   *    it doesn't guarantee the stability of the result it produces.
-   *  - subscriber numbers may not be diallable from all devices (notably mobile devices, which
-   *    typically requires the full national_number to be dialled in most countries).
-   *  - most non-geographical numbers have no area codes.
-   *  - some geographical numbers have no area codes.
-   *
+   * using it for most purposes, but recommends using the more general {@code national_number}
+   * instead. Read the following carefully before deciding to use this method:
+   * <ul>
+   *  <li> geographical area codes change over time, and this method honors those changes;
+   *    therefore, it doesn't guarantee the stability of the result it produces.
+   *  <li> subscriber numbers may not be diallable from all devices (notably mobile devices, which
+   *    typically requires the full national_number to be dialled in most regions).
+   *  <li> most non-geographical numbers have no area codes.
+   *  <li> 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.
@@ -663,7 +636,7 @@
    * </pre>
    *
    * Refer to the unittests to see the difference between this function and
-   * {@link #getLengthOfGeographicalAreaCode()}.
+   * {@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.
@@ -780,7 +753,7 @@
    * Helper function to check region code is not unknown or null.
    */
   private boolean isValidRegionCode(String regionCode) {
-    return regionCode != null && supportedRegions.contains(regionCode.toUpperCase());
+    return regionCode != null && supportedRegions.contains(regionCode);
   }
 
   /**
@@ -819,25 +792,27 @@
     return formattedNumber.toString();
   }
 
-  // Same as format(PhoneNumber, PhoneNumberFormat), but accepts mutable StringBuilder as parameters
-  // to decrease object creation when invoked many times.
+  /**
+   * Same as {@link #format(PhoneNumber, PhoneNumberFormat)}, but accepts a mutable StringBuilder as
+   * a parameter to decrease object creation when invoked many times.
+   */
   public void format(PhoneNumber number, PhoneNumberFormat numberFormat,
                      StringBuilder formattedNumber) {
     // Clear the StringBuilder first.
     formattedNumber.setLength(0);
-    int countryCode = number.getCountryCode();
+    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.
       formattedNumber.append(nationalSignificantNumber);
-      formatNumberByFormat(countryCode, PhoneNumberFormat.E164, formattedNumber);
+      formatNumberByFormat(countryCallingCode, PhoneNumberFormat.E164, formattedNumber);
       return;
     }
     // Note getRegionCodeForCountryCode() is used because formatting information for regions which
     // share a country calling code is contained by only one region for performance reasons. For
     // example, for NANPA regions it will be contained in the metadata for US.
-    String regionCode = getRegionCodeForCountryCode(countryCode);
+    String regionCode = getRegionCodeForCountryCode(countryCallingCode);
     if (!isValidRegionCode(regionCode)) {
       formattedNumber.append(nationalSignificantNumber);
       return;
@@ -846,7 +821,7 @@
     formattedNumber.append(formatNationalNumber(nationalSignificantNumber,
                                                 regionCode, numberFormat));
     maybeGetFormattedExtension(number, regionCode, numberFormat, formattedNumber);
-    formatNumberByFormat(countryCode, numberFormat, formattedNumber);
+    formatNumberByFormat(countryCallingCode, numberFormat, formattedNumber);
   }
 
   /**
@@ -969,7 +944,7 @@
   /**
    * Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
    * supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
-   * same as the region where the number is from, then NATIONAL formatting will be applied.
+   * same as that of the region where the number is from, then NATIONAL formatting will be applied.
    *
    * <p>If the number itself has a country calling code of zero or an otherwise invalid country
    * calling code, then we return the number with no formatting applied.
@@ -980,18 +955,18 @@
    * INTERNATIONAL format will be returned instead.
    *
    * @param number               the phone number to be formatted
-   * @param regionCallingFrom    the ISO 3166-1 two-letter region code that denotes the region
-   *                             where the call is being placed
+   * @param regionCallingFrom    the region where the call is being placed
    * @return  the formatted phone number
    */
-  public String formatOutOfCountryCallingNumber(PhoneNumber number, String regionCallingFrom) {
+  public String formatOutOfCountryCallingNumber(PhoneNumber number,
+                                                String regionCallingFrom) {
     if (!isValidRegionCode(regionCallingFrom)) {
       return format(number, PhoneNumberFormat.INTERNATIONAL);
     }
     int countryCallingCode = number.getCountryCode();
     String regionCode = getRegionCodeForCountryCode(countryCallingCode);
     String nationalSignificantNumber = getNationalSignificantNumber(number);
-    if (!isValidRegionCode(regionCode)) {
+    if (!hasValidRegionCode(regionCode, countryCallingCode, nationalSignificantNumber)) {
       return nationalSignificantNumber;
     }
     if (countryCallingCode == NANPA_COUNTRY_CODE) {
@@ -1090,7 +1065,8 @@
    * @param regionCallingFrom  the region where the call is being placed
    * @return  the formatted phone number
    */
-  public String formatOutOfCountryKeepingAlphaChars(PhoneNumber number, String regionCallingFrom) {
+  public String formatOutOfCountryKeepingAlphaChars(PhoneNumber number,
+                                                    String regionCallingFrom) {
     String rawInput = number.getRawInput();
     // If there is no raw input, then we can't keep alpha characters because there aren't any.
     // In this case, we return formatOutOfCountryCallingNumber.
@@ -1169,7 +1145,7 @@
    * Gets the national significant number of the a phone number. Note a national significant number
    * doesn't contain a national prefix or any formatting.
    *
-   * @param number  the PhoneNumber object for which the national significant number is needed
+   * @param number  the phone number for which the national significant number is needed
    * @return  the national significant number of the PhoneNumber object passed in
    */
   public String getNationalSignificantNumber(PhoneNumber number) {
@@ -1181,7 +1157,7 @@
     // Other regions such as Cote d'Ivoire and Gabon use this for their mobile numbers.
     StringBuilder nationalNumber = new StringBuilder(
         (number.hasItalianLeadingZero() &&
-         number.getItalianLeadingZero() &&
+         number.isItalianLeadingZero() &&
          isLeadingZeroPossible(number.getCountryCode()))
         ? "0" : ""
     );
@@ -1299,8 +1275,7 @@
   /**
    * Gets a valid number for the specified region.
    *
-   * @param regionCode  the ISO 3166-1 two-letter region code that denotes
-   *                    the region for which an example number is needed
+   * @param regionCode  the region for which an example number is needed
    * @return  a valid fixed-line number for the specified region. Returns null when the metadata
    *    does not contain such information.
    */
@@ -1311,11 +1286,10 @@
   /**
    * Gets a valid number for the specified region and number type.
    *
-   * @param regionCode  the ISO 3166-1 two-letter region code that denotes
-   *                    the region for which an example number is needed
+   * @param regionCode  the region for which an example number is needed
    * @param type  the type of number that is needed
    * @return  a valid number for the specified region and type. Returns null when the metadata
-   *     does not contain such information.
+   *     does not contain such information or if an invalid region was entered.
    */
   public PhoneNumber getExampleNumberForType(String regionCode, PhoneNumberType type) {
     // Check the region code is valid.
@@ -1355,7 +1329,8 @@
    * prefix. This will be the default extension prefix, unless overridden by a preferred
    * extension prefix for this region.
    */
-  private void formatExtension(String extensionDigits, String regionCode, StringBuilder extension) {
+  private void formatExtension(String extensionDigits, String regionCode,
+                               StringBuilder extension) {
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
     if (metadata.hasPreferredExtnPrefix()) {
       extension.append(metadata.getPreferredExtnPrefix()).append(extensionDigits);
@@ -1436,7 +1411,7 @@
 
     boolean isFixedLine = isNumberMatchingDesc(nationalNumber, metadata.getFixedLine());
     if (isFixedLine) {
-      if (metadata.getSameMobileAndFixedLinePattern()) {
+      if (metadata.isSameMobileAndFixedLinePattern()) {
         return PhoneNumberType.FIXED_LINE_OR_MOBILE;
       } else if (isNumberMatchingDesc(nationalNumber, metadata.getMobile())) {
         return PhoneNumberType.FIXED_LINE_OR_MOBILE;
@@ -1445,7 +1420,7 @@
     }
     // Otherwise, test to see if the number is mobile. Only do this if certain that the patterns for
     // mobile and fixed line aren't the same.
-    if (!metadata.getSameMobileAndFixedLinePattern() &&
+    if (!metadata.isSameMobileAndFixedLinePattern() &&
         isNumberMatchingDesc(nationalNumber, metadata.getMobile())) {
       return PhoneNumberType.MOBILE;
     }
@@ -1456,7 +1431,6 @@
     if (!isValidRegionCode(regionCode)) {
       return null;
     }
-    regionCode = regionCode.toUpperCase();
     if (!regionToMetadataMap.containsKey(regionCode)) {
       loadMetadataForRegionFromFile(currentFilePrefix, regionCode);
     }
@@ -1494,8 +1468,7 @@
    * Canada, rather than just a valid NANPA number.
    *
    * @param number       the phone number that we want to validate
-   * @param regionCode   the ISO 3166-1 two-letter region code that denotes the region that we want
-   *                     to validate the phone number for
+   * @param regionCode   the region that we want to validate the phone number for
    * @return  a boolean that indicates whether the number is of a valid pattern
    */
   public boolean isValidNumberForRegion(PhoneNumber number, String regionCode) {
@@ -1569,8 +1542,7 @@
    * Returns the country calling code for a specific region. For example, this would be 1 for the
    * United States, and 64 for New Zealand.
    *
-   * @param regionCode  the ISO 3166-1 two-letter region code that denotes
-   *                    the region that we want to get the country calling code for
+   * @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
    */
   public int getCountryCodeForRegion(String regionCode) {
@@ -1591,8 +1563,7 @@
    * national dialling prefix is used only for certain types of numbers. Use the library's
    * formatting functions to prefix the national prefix when required.
    *
-   * @param regionCode  the ISO 3166-1 two-letter region code that denotes
-   *                    the region that we want to get the dialling prefix for
+   * @param regionCode  the region that we want to get the dialling prefix for
    * @param stripNonDigits  true to strip non-digits from the national dialling prefix
    * @return  the dialling prefix for the region denoted by regionCode
    */
@@ -1621,7 +1592,7 @@
    * @return  true if regionCode is one of the regions under NANPA
    */
   public boolean isNANPACountry(String regionCode) {
-    return regionCode != null && nanpaRegions.contains(regionCode.toUpperCase());
+    return regionCode != null && nanpaRegions.contains(regionCode);
   }
 
   /**
@@ -1745,8 +1716,7 @@
    * number)} with the resultant PhoneNumber object.
    *
    * @param number  the number that needs to be checked, in the form of a string
-   * @param regionDialingFrom  the ISO 3166-1 two-letter region code that denotes the region that
-   *     we are expecting the number to be dialed from.
+   * @param regionDialingFrom  the region that we are expecting the number to be dialed from.
    *     Note this is different from the region where the number belongs.  For example, the number
    *     +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
    *     dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
@@ -1793,9 +1763,7 @@
   /**
    * Gets an {@link com.google.i18n.phonenumbers.AsYouTypeFormatter} for the specific region.
    *
-   * @param regionCode  the ISO 3166-1 two-letter region code that denotes the region where
-   *     the phone number is being entered
-   *
+   * @param regionCode  the region where the phone number is being entered
    * @return  an {@link com.google.i18n.phonenumbers.AsYouTypeFormatter} object, which can be used
    *     to format phone numbers in the specific region "as you type"
    */
@@ -1935,7 +1903,7 @@
       // cannot begin with 0.
       Matcher digitMatcher = CAPTURING_DIGIT_PATTERN.matcher(number.substring(matchEnd));
       if (digitMatcher.find()) {
-        String normalizedGroup = normalizeHelper(digitMatcher.group(1), DIGIT_MAPPINGS, true);
+        String normalizedGroup = normalizeDigitsOnly(digitMatcher.group(1));
         if (normalizedGroup.equals("0")) {
           return false;
         }
@@ -2092,13 +2060,12 @@
    *
    * @param numberToParse     number that we are attempting to parse. This can contain formatting
    *                          such as +, ( and -, as well as a phone number extension.
-   * @param defaultRegion     the ISO 3166-1 two-letter region code that denotes the 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.
+   * @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
    *                          of the default region supplied. If the number is guaranteed to
-   *                          start with a '+' followed by the country calling code, then "ZZ" or
-   *                          null can be supplied.
+   *                          start with a '+' followed by the country calling code, then
+   *                          "ZZ" or null can be supplied.
    * @return                  a phone number proto buffer filled with the parsed number
    * @throws NumberParseException  if the string is not considered to be a viable phone number or if
    *                               no default region was supplied and the number is not in
@@ -2111,8 +2078,10 @@
     return phoneNumber;
   }
 
-  // Same as parse(String, String), but accepts mutable PhoneNumber as a parameter to
-  // decrease object creation when invoked many times.
+  /**
+   * Same as {@link #parse(String, String)}, but accepts mutable PhoneNumber as a parameter to
+   * decrease object creation when invoked many times.
+   */
   public void parse(String numberToParse, String defaultRegion, PhoneNumber phoneNumber)
       throws NumberParseException {
     parseHelper(numberToParse, defaultRegion, false, true, phoneNumber);
@@ -2125,11 +2094,10 @@
    *
    * @param numberToParse     number that we are attempting to parse. This can contain formatting
    *                          such as +, ( and -, as well as a phone number extension.
-   * @param defaultRegion     the ISO 3166-1 two-letter region code that denotes the 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 calling code for the number in this case would be stored as
-   *                          that of the default region supplied.
+   * @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 calling code for the number in this case would be stored
+   *                          as that of the default region supplied.
    * @return                  a phone number proto buffer filled with the parsed number
    * @throws NumberParseException  if the string is not considered to be a viable phone number or if
    *                               no default region was supplied
@@ -2141,8 +2109,10 @@
     return phoneNumber;
   }
 
-  // Same as parseAndKeepRawInput(String, String), but accepts mutable PhoneNumber as a parameter to
-  // decrease object creation when invoked many times.
+  /**
+   * Same as{@link #parseAndKeepRawInput(String, String)}, but accepts a mutable PhoneNumber as
+   * a parameter to decrease object creation when invoked many times.
+   */
   public void parseAndKeepRawInput(String numberToParse, String defaultRegion,
                                    PhoneNumber phoneNumber)
       throws NumberParseException {
@@ -2155,11 +2125,10 @@
    * getMatcher(text, defaultRegion, Leniency.VALID, Long.MAX_VALUE)}.
    *
    * @param text              the text to search for phone numbers, null for no text
-   * @param defaultRegion     the ISO 3166-1 two-letter region code that denotes the 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 calling code for the number in this case would be stored as
-   *                          that of the default region supplied. May be null if only international
+   * @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 of
+   *                          the default region supplied. May be null if only international
    *                          numbers are expected.
    */
   public Iterable<PhoneNumberMatch> findNumbers(CharSequence text, String defaultRegion) {
@@ -2170,11 +2139,10 @@
    * Returns an iterable over all {@link PhoneNumberMatch PhoneNumberMatches} in {@code text}.
    *
    * @param text              the text to search for phone numbers, null for no text
-   * @param defaultRegion    the ISO 3166-1 two-letter region code that denotes the 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 calling code for the number in this case would be stored as
-   *                          that of the default region supplied. May be null if only international
+   * @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 of
+   *                          the default region supplied. May be null if only international
    *                          numbers are expected.
    * @param leniency          the leniency to use when evaluating candidate phone numbers
    * @param maxTries          the maximum number of invalid numbers to try before giving up on the
@@ -2410,7 +2378,7 @@
 
   /**
    * Takes two phone numbers and compares them for equality. This is a convenience wrapper for
-   * isNumberMatch(PhoneNumber firstNumber, PhoneNumber secondNumber). No default region is known.
+   * {@link #isNumberMatch(PhoneNumber, PhoneNumber)}. No default region is known.
    *
    * @param firstNumber  first number to compare in proto buffer format.
    * @param secondNumber  second number to compare. Can contain formatting, and can have country
@@ -2467,7 +2435,7 @@
   boolean canBeInternationallyDialled(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
     String nationalSignificantNumber = getNationalSignificantNumber(number);
-    if (!isValidRegionCode(regionCode)) {
+    if (!hasValidRegionCode(regionCode, number.getCountryCode(), nationalSignificantNumber)) {
       return true;
     }
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
diff --git a/java/src/com/google/i18n/phonenumbers/Phonemetadata.java b/java/src/com/google/i18n/phonenumbers/Phonemetadata.java
index de9f7c0..34afaff 100644
--- a/java/src/com/google/i18n/phonenumbers/Phonemetadata.java
+++ b/java/src/com/google/i18n/phonenumbers/Phonemetadata.java
@@ -536,7 +536,7 @@
     private boolean hasSameMobileAndFixedLinePattern;
     private boolean sameMobileAndFixedLinePattern_ = false;
     public boolean hasSameMobileAndFixedLinePattern() { return hasSameMobileAndFixedLinePattern; }
-    public boolean getSameMobileAndFixedLinePattern() { return sameMobileAndFixedLinePattern_; }
+    public boolean isSameMobileAndFixedLinePattern() { return sameMobileAndFixedLinePattern_; }
     public PhoneMetadata setSameMobileAndFixedLinePattern(boolean value) {
       hasSameMobileAndFixedLinePattern = true;
       sameMobileAndFixedLinePattern_ = value;
@@ -588,6 +588,9 @@
     private boolean mainCountryForCode_ = false;
     public boolean hasMainCountryForCode() { return hasMainCountryForCode; }
     public boolean isMainCountryForCode() { return mainCountryForCode_; }
+    // Method that lets this class have the same interface as the one generated by Protocol Buffers
+    // which is used by C++ build tools.
+    public boolean getMainCountryForCode() { return mainCountryForCode_; }
     public PhoneMetadata setMainCountryForCode(boolean value) {
       hasMainCountryForCode = true;
       mainCountryForCode_ = value;
diff --git a/java/src/com/google/i18n/phonenumbers/Phonenumber.java b/java/src/com/google/i18n/phonenumbers/Phonenumber.java
index 3f7d23e..3d59d52 100644
--- a/java/src/com/google/i18n/phonenumbers/Phonenumber.java
+++ b/java/src/com/google/i18n/phonenumbers/Phonenumber.java
@@ -94,7 +94,7 @@
     private boolean hasItalianLeadingZero;
     private boolean italianLeadingZero_ = false;
     public boolean hasItalianLeadingZero() { return hasItalianLeadingZero; }
-    public boolean getItalianLeadingZero() { return italianLeadingZero_; }
+    public boolean isItalianLeadingZero() { return italianLeadingZero_; }
     public PhoneNumber setItalianLeadingZero(boolean value) {
       hasItalianLeadingZero = true;
       italianLeadingZero_ = value;
@@ -185,7 +185,7 @@
         setExtension(other.getExtension());
       }
       if (other.hasItalianLeadingZero()) {
-        setItalianLeadingZero(other.getItalianLeadingZero());
+        setItalianLeadingZero(other.isItalianLeadingZero());
       }
       if (other.hasRawInput()) {
         setRawInput(other.getRawInput());
@@ -228,7 +228,7 @@
       hash = (53 * hash) + getCountryCode();
       hash = (53 * hash) + Long.valueOf(getNationalNumber()).hashCode();
       hash = (53 * hash) + getExtension().hashCode();
-      hash = (53 * hash) + (getItalianLeadingZero() ? 1231 : 1237);
+      hash = (53 * hash) + (isItalianLeadingZero() ? 1231 : 1237);
       hash = (53 * hash) + getRawInput().hashCode();
       hash = (53 * hash) + getCountryCodeSource().hashCode();
       hash = (53 * hash) + getPreferredDomesticCarrierCode().hashCode();
@@ -241,7 +241,7 @@
       StringBuilder outputString = new StringBuilder();
       outputString.append("Country Code: ").append(countryCode_);
       outputString.append(" National Number: ").append(nationalNumber_);
-      if (hasItalianLeadingZero() && getItalianLeadingZero()) {
+      if (hasItalianLeadingZero() && isItalianLeadingZero()) {
         outputString.append(" Leading Zero: true");
       }
       if (hasExtension()) {
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BG b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BG
index e1e34d9..f3645fd 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BG
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BG
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EG b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EG
index e3a1c56..a00edb4 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EG
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EG
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES
index 2ae03c9..c5717d0 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GH b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GH
index 64e2f87..4e97d13 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GH
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GH
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KZ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KZ
index bf98d95..06708a2 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KZ
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_KZ
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PF b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PF
index cd67c8b..f09ce4c 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PF
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PF
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC
index ff47679..95fa89a 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SC
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SY b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SY
index b2256b3..858b5da 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SY
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SY
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US
index 7f80f1d..b0b6597 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VU b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VU
index 40c22cd..fe110d2 100644
--- a/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VU
+++ b/java/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VU
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java b/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
new file mode 100644
index 0000000..644c14b
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.geocoding;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * A utility that maps phone number prefixes to a string describing the geographical area the prefix
+ * covers.
+ *
+ * @author Shaopeng Jia
+ */
+public class AreaCodeMap implements Externalizable {
+  private int numOfEntries = 0;
+  private TreeSet<Integer> possibleLengths = new TreeSet<Integer>();
+  private int[] phoneNumberPrefixes;
+  private String[] descriptions;
+  private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
+
+  /**
+   * Creates an empty {@link AreaCodeMap}. The default constructor is necessary for implementing
+   * {@link Externalizable}. The empty map could later populated by
+   * {@link #readAreaCodeMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}.
+   */
+  public AreaCodeMap() {}
+
+  /**
+   * Creates an {@link AreaCodeMap} initialized with {@code sortedAreaCodeMap}.
+   *
+   * @param sortedAreaCodeMap  a map from phone number prefixes to descriptions of corresponding
+   *     geographical areas, sorted in ascending order of the phone number prefixes as integers.
+   */
+  public void readAreaCodeMap(SortedMap<Integer, String> sortedAreaCodeMap) {
+    numOfEntries = sortedAreaCodeMap.size();
+    phoneNumberPrefixes = new int[numOfEntries];
+    descriptions = new String[numOfEntries];
+    int index = 0;
+    for (int prefix : sortedAreaCodeMap.keySet()) {
+      phoneNumberPrefixes[index++] = prefix;
+      possibleLengths.add((int) Math.log10(prefix) + 1);
+    }
+    sortedAreaCodeMap.values().toArray(descriptions);
+  }
+
+  /**
+   * Supports Java Serialization.
+   */
+  public void readExternal(ObjectInput objectInput) throws IOException {
+    numOfEntries = objectInput.readInt();
+    if (phoneNumberPrefixes == null || phoneNumberPrefixes.length < numOfEntries) {
+      phoneNumberPrefixes = new int[numOfEntries];
+    }
+    if (descriptions == null || descriptions.length < numOfEntries) {
+      descriptions = new String[numOfEntries];
+    }
+    for (int i = 0; i < numOfEntries; i++) {
+      phoneNumberPrefixes[i] = objectInput.readInt();
+      descriptions[i] = objectInput.readUTF();
+    }
+    int sizeOfLengths = objectInput.readInt();
+    possibleLengths.clear();
+    for (int i = 0; i < sizeOfLengths; i++) {
+      possibleLengths.add(objectInput.readInt());
+    }
+  }
+
+  /**
+   * Supports Java Serialization.
+   */
+  public void writeExternal(ObjectOutput objectOutput) throws IOException {
+    objectOutput.writeInt(numOfEntries);
+    for (int i = 0; i < numOfEntries; i++) {
+      objectOutput.writeInt(phoneNumberPrefixes[i]);
+      objectOutput.writeUTF(descriptions[i]);
+    }
+    int sizeOfLengths = possibleLengths.size();
+    objectOutput.writeInt(sizeOfLengths);
+    for (Integer length : possibleLengths) {
+      objectOutput.writeInt(length);
+    }
+  }
+
+  /**
+   * Returns the description of the geographical area the {@code number} corresponds to.
+   *
+   * @param number  the phone number to look up
+   * @return  the description of the geographical area
+   */
+  String lookup(PhoneNumber number) {
+    if (numOfEntries == 0) {
+      return "";
+    }
+    long phonePrefix =
+        Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
+    int currentIndex = numOfEntries - 1;
+    SortedSet<Integer> currentSetOfLengths = possibleLengths;
+    while (currentSetOfLengths.size() > 0) {
+      Integer possibleLength = currentSetOfLengths.last();
+      String phonePrefixStr = String.valueOf(phonePrefix);
+      if (phonePrefixStr.length() > possibleLength) {
+        phonePrefix = Long.parseLong(phonePrefixStr.substring(0, possibleLength));
+      }
+      currentIndex = binarySearch(0, currentIndex, phonePrefix);
+      if (currentIndex < 0) {
+        return "";
+      }
+      if (phonePrefix == phoneNumberPrefixes[currentIndex]) {
+        return descriptions[currentIndex];
+      }
+      currentSetOfLengths = possibleLengths.headSet(possibleLength);
+    }
+    return "";
+  }
+
+  /**
+   * Does a binary search for {@code value} in the phoneNumberPrefixes array from {@code start} to
+   * {@code end} (inclusive). Returns the position if {@code value} is found; otherwise, returns the
+   * position which has the largest value that is less than {@code value}. This means if
+   * {@code value} is the smallest, -1 will be returned.
+   */
+  private int binarySearch(int start, int end, long value) {
+    int current = 0;
+    while (start <= end) {
+      current = (start + end) / 2;
+      if (phoneNumberPrefixes[current] == value) {
+        return current;
+      } else if (phoneNumberPrefixes[current] > value) {
+        current--;
+        end = current;
+      } else {
+        start = current + 1;
+      }
+    }
+    return current;
+  }
+
+  /**
+   * Dumps the mappings contained in the area code map.
+   */
+  @Override
+  public String toString() {
+    StringBuilder output = new StringBuilder();
+    for (int i = 0; i < numOfEntries; i++) {
+      output.append(phoneNumberPrefixes[i]);
+      output.append("|");
+      output.append(descriptions[i]);
+      output.append("\n");
+    }
+    return output.toString();
+  }
+}
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/MappingFileProvider.java b/java/src/com/google/i18n/phonenumbers/geocoding/MappingFileProvider.java
new file mode 100644
index 0000000..4f04a5d
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/MappingFileProvider.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.geocoding;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * A utility which knows the data files that are available for the geocoder to use. The data files
+ * contain mappings from phone number prefixes to text descriptions, and are organized by country
+ * calling code and language that the text descriptions are in.
+ *
+ * @author Shaopeng Jia
+ */
+public class MappingFileProvider implements Externalizable {
+  private int numOfEntries = 0;
+  private int[] countryCallingCodes;
+  private List<Set<String>> availableLanguages;
+  private static final Map<String, String> LOCALE_NORMALIZATION_MAP;
+
+  static {
+    Map<String, String> normalizationMap = new HashMap<String, String>();
+    normalizationMap.put("zh_TW", "zh_Hant");
+    normalizationMap.put("zh_HK", "zh_Hant");
+    normalizationMap.put("zh_MO", "zh_Hant");
+
+    LOCALE_NORMALIZATION_MAP = Collections.unmodifiableMap(normalizationMap);
+  }
+
+  /**
+   * Creates an empty {@link MappingFileProvider}. The default constructor is necessary for
+   * implementing {@link Externalizable}. The empty provider could later be populated by
+   * {@link #readFileConfigs(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}.
+   */
+  public MappingFileProvider() {
+  }
+
+  /**
+   * Initializes an {@link MappingFileProvider} with {@code availableDataFiles}.
+   *
+   * @param availableDataFiles  a map from country calling codes to sets of languages in which data
+   *     files are available for the specific country calling code. The map is sorted in ascending
+   *     order of the country calling codes as integers.
+   */
+  public void readFileConfigs(SortedMap<Integer, Set<String>> availableDataFiles) {
+    numOfEntries = availableDataFiles.size();
+    countryCallingCodes = new int[numOfEntries];
+    availableLanguages = new ArrayList<Set<String>>(numOfEntries);
+    int index = 0;
+    for (int countryCallingCode : availableDataFiles.keySet()) {
+      countryCallingCodes[index++] = countryCallingCode;
+      availableLanguages.add(new HashSet<String>(availableDataFiles.get(countryCallingCode)));
+    }
+  }
+
+  /**
+   * Supports Java Serialization.
+   */
+  public void readExternal(ObjectInput objectInput) throws IOException {
+    numOfEntries = objectInput.readInt();
+    if (countryCallingCodes == null || countryCallingCodes.length < numOfEntries) {
+      countryCallingCodes = new int[numOfEntries];
+    }
+    if (availableLanguages == null) {
+      availableLanguages = new ArrayList<Set<String>>();
+    }
+    for (int i = 0; i < numOfEntries; i++) {
+      countryCallingCodes[i] = objectInput.readInt();
+      int numOfLangs = objectInput.readInt();
+      Set<String> setOfLangs = new HashSet<String>();
+      for (int j = 0; j < numOfLangs; j++) {
+        setOfLangs.add(objectInput.readUTF());
+      }
+      availableLanguages.add(setOfLangs);
+    }
+  }
+
+  /**
+   * Supports Java Serialization.
+   */
+  public void writeExternal(ObjectOutput objectOutput) throws IOException {
+    objectOutput.writeInt(numOfEntries);
+    for (int i = 0; i < numOfEntries; i++) {
+      objectOutput.writeInt(countryCallingCodes[i]);
+      Set<String> setOfLangs = availableLanguages.get(i);
+      int numOfLangs = setOfLangs.size();
+      objectOutput.writeInt(numOfLangs);
+      for (String lang : setOfLangs) {
+        objectOutput.writeUTF(lang);
+      }
+    }
+  }
+
+  /**
+   * Returns a string representing the data in this class. The string contains one line for each
+   * country calling code. The country calling code is followed by a '|' and then a list of
+   * comma-separated languages sorted in ascending order.
+   */
+  @Override
+  public String toString() {
+    StringBuilder output = new StringBuilder();
+    for (int i = 0; i < numOfEntries; i++) {
+      output.append(countryCallingCodes[i]);
+      output.append('|');
+      SortedSet<String> sortedSetOfLangs = new TreeSet<String>(availableLanguages.get(i));
+      for (String lang : sortedSetOfLangs) {
+        output.append(lang);
+        output.append(',');
+      }
+      output.append('\n');
+    }
+    return output.toString();
+  }
+
+  /**
+   * Gets the name of the file that contains the mapping data for the {@code countryCallingCode} in
+   * the language specified.
+   *
+   * @param countryCallingCode  the country calling code of phone numbers which the data file
+   *     contains
+   * @param language  two-letter lowercase ISO language codes as defined by ISO 639-1
+   * @param script  four-letter titlecase (the first letter is uppercase and the rest of the letters
+   *     are lowercase) ISO script codes as defined in ISO 15924
+   * @param region  two-letter uppercase ISO country codes as defined by ISO 3166-1
+   * @return  the name of the file, or empty string if no such file can be found
+   */
+  String getFileName(int countryCallingCode, String language, String script, String region) {
+    if (language.length() == 0) {
+      return "";
+    }
+    int index = Arrays.binarySearch(countryCallingCodes, countryCallingCode);
+    if (index < 0) {
+      return "";
+    }
+    Set<String> setOfLangs = availableLanguages.get(index);
+    if (setOfLangs.size() > 0) {
+      String languageCode = findBestMatchingLanguageCode(setOfLangs, language, script, region);
+      if (languageCode.length() > 0) {
+        StringBuilder fileName = new StringBuilder();
+        fileName.append(countryCallingCode).append('_').append(languageCode);
+        return fileName.toString();
+      }
+    }
+    return "";
+  }
+
+  private String findBestMatchingLanguageCode(
+      Set<String> setOfLangs, String language, String script, String region) {
+    StringBuilder fullLocale = constructFullLocale(language, script, region);
+    String fullLocaleStr = fullLocale.toString();
+    String normalizedLocale = LOCALE_NORMALIZATION_MAP.get(fullLocaleStr);
+    if (normalizedLocale != null) {
+      if (setOfLangs.contains(normalizedLocale)) {
+        return normalizedLocale;
+      }
+    }
+    if (setOfLangs.contains(fullLocaleStr)) {
+      return fullLocaleStr;
+    }
+
+    if (onlyOneOfScriptOrRegionIsEmpty(script, region)) {
+      if (setOfLangs.contains(language)) {
+        return language;
+      }
+    } else if (script.length() > 0 && region.length() > 0) {
+      StringBuilder langWithScript = new StringBuilder(language).append('_').append(script);
+      String langWithScriptStr = langWithScript.toString();
+      if (setOfLangs.contains(langWithScriptStr)) {
+        return langWithScriptStr;
+      }
+
+      StringBuilder langWithRegion = new StringBuilder(language).append('_').append(region);
+      String langWithRegionStr = langWithRegion.toString();
+      if (setOfLangs.contains(langWithRegionStr)) {
+        return langWithRegionStr;
+      }
+
+      if (setOfLangs.contains(language)) {
+        return language;
+      }
+    }
+    return "";
+  }
+
+  private boolean onlyOneOfScriptOrRegionIsEmpty(String script, String region) {
+    return (script.length() == 0 && region.length() > 0) ||
+            (region.length() == 0 && script.length() > 0);
+  }
+
+  private StringBuilder constructFullLocale(String language, String script, String region) {
+    StringBuilder fullLocale = new StringBuilder(language);
+    appendSubsequentLocalePart(script, fullLocale);
+    appendSubsequentLocalePart(region, fullLocale);
+    return fullLocale;
+  }
+
+  private void appendSubsequentLocalePart(String subsequentLocalePart, StringBuilder fullLocale) {
+    if (subsequentLocalePart.length() > 0) {
+      fullLocale.append('_').append(subsequentLocalePart);
+    }
+  }
+}
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java b/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
new file mode 100644
index 0000000..1489b6b
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.geocoding;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An offline geocoder which provides geographical information related to a phone number.
+ *
+ * @author Shaopeng Jia
+ */
+public class PhoneNumberOfflineGeocoder {
+  private static PhoneNumberOfflineGeocoder instance = null;
+  private static final String MAPPING_DATA_DIRECTORY =
+      "/com/google/i18n/phonenumbers/geocoding/data/";
+  private static final Logger LOGGER = Logger.getLogger(PhoneNumberOfflineGeocoder.class.getName());
+
+  private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
+  private final String phonePrefixDataDirectory;
+
+  // The mappingFileProvider knows for which combination of countryCallingCode and language a phone
+  // prefix mapping file is available in the file system, so that a file can be loaded when needed.
+  private MappingFileProvider mappingFileProvider = new MappingFileProvider();
+
+  // A mapping from countryCallingCode_lang to the corresponding phone prefix map that has been
+  // loaded.
+  private Map<String, AreaCodeMap> availablePhonePrefixMaps = new HashMap<String, AreaCodeMap>();
+
+  /**
+   * For testing purposes, we allow the phone number util variable to be injected.
+   *
+   * @VisibleForTesting
+   */
+  PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
+    this.phonePrefixDataDirectory = phonePrefixDataDirectory;
+    loadMappingFileProvider();
+  }
+
+  private void loadMappingFileProvider() {
+    InputStream source =
+        PhoneNumberOfflineGeocoder.class.getResourceAsStream(phonePrefixDataDirectory + "config");
+    ObjectInputStream in;
+    try {
+      in = new ObjectInputStream(source);
+      mappingFileProvider.readExternal(in);
+    } catch (IOException e) {
+      LOGGER.log(Level.WARNING, e.toString());
+    }
+  }
+
+  private AreaCodeMap getPhonePrefixDescriptions(
+      int countryCallingCode, String language, String script, String region) {
+    String fileName = mappingFileProvider.getFileName(countryCallingCode, language, script, region);
+    if (fileName.length() == 0) {
+      return null;
+    }
+    if (!availablePhonePrefixMaps.containsKey(fileName)) {
+      loadAreaCodeMapFromFile(fileName);
+    }
+    return availablePhonePrefixMaps.get(fileName);
+  }
+
+  private void loadAreaCodeMapFromFile(String fileName) {
+    InputStream source =
+        PhoneNumberOfflineGeocoder.class.getResourceAsStream(phonePrefixDataDirectory + fileName);
+    ObjectInputStream in;
+    try {
+      in = new ObjectInputStream(source);
+      AreaCodeMap map = new AreaCodeMap();
+      map.readExternal(in);
+      availablePhonePrefixMaps.put(fileName, map);
+    } catch (IOException e) {
+      LOGGER.log(Level.WARNING, e.toString());
+    }
+  }
+
+  /**
+   * Gets a {@link PhoneNumberOfflineGeocoder} instance to carry out international phone number
+   * geocoding.
+   *
+   * <p> The {@link PhoneNumberOfflineGeocoder} is implemented as a singleton. Therefore, calling
+   * this method multiple times will only result in one instance being created.
+   *
+   * @return  a {@link PhoneNumberOfflineGeocoder} instance
+   */
+  public static synchronized PhoneNumberOfflineGeocoder getInstance() {
+    if (instance == null) {
+      instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
+    }
+    return instance;
+  }
+
+  /**
+   * Preload the data file for the given language and country calling code, so that a future lookup
+   * for this language and country calling code will not incur any file loading.
+   *
+   * @param locale  specifies the language of the data file to load
+   * @param countryCallingCode   specifies the country calling code of phone numbers that are
+   *     contained by the file to be loaded
+   */
+  public void loadDataFile(Locale locale, int countryCallingCode) {
+    instance.getPhonePrefixDescriptions(countryCallingCode, locale.getLanguage(), "",
+        locale.getCountry());
+  }
+
+  /**
+   * Returns the customary display name in the given language for the given territory the phone
+   * number is from.
+   */
+  private String getCountryNameForNumber(PhoneNumber number, Locale language) {
+    String regionCode = phoneUtil.getRegionCodeForNumber(number);
+    return (regionCode == null || regionCode.equals("ZZ"))
+        ? "" : new Locale("", regionCode).getDisplayCountry(language);
+  }
+
+  /**
+   * Returns a text description for the given language code for the given phone number. The
+   * description might consist of the name of the country where the phone number is from and/or the
+   * name of the geographical area the phone number is from.
+   *
+   * @param number  the phone number for which we want to get a text description
+   * @param languageCode  the language code for which the description should be written
+   * @return  a text description for the given language code for the given phone number
+   */
+  public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
+    String areaDescription =
+        getAreaDescriptionForNumber(
+            number, languageCode.getLanguage(), "",  // No script is specified.
+            languageCode.getCountry());
+    return (areaDescription.length() > 0)
+        ? areaDescription : getCountryNameForNumber(number, languageCode);
+  }
+
+  /**
+   * Returns an area-level text description in the given language for the given phone number.
+   *
+   * @param number  the phone number for which we want to get a text description
+   * @param lang  two-letter lowercase ISO language codes as defined by ISO 639-1
+   * @param script  four-letter titlecase (the first letter is uppercase and the rest of the letters
+   *     are lowercase) ISO script codes as defined in ISO 15924
+   * @param region  two-letter uppercase ISO country codes as defined by ISO 3166-1
+   * @return  an area-level text description in the given language for the given phone number, or an
+   *     empty string if such a description is not available
+   */
+  private String getAreaDescriptionForNumber(
+      PhoneNumber number, String lang, String script, String region) {
+    AreaCodeMap phonePrefixDescriptions =
+        getPhonePrefixDescriptions(number.getCountryCode(), lang, script, region);
+    return (phonePrefixDescriptions != null) ? phonePrefixDescriptions.lookup(number) : "";
+  }
+}
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/1_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/1_en
new file mode 100644
index 0000000..d69ae59
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/1_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/31_nl b/java/src/com/google/i18n/phonenumbers/geocoding/data/31_nl
new file mode 100644
index 0000000..f967ac9
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/31_nl
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/34_es b/java/src/com/google/i18n/phonenumbers/geocoding/data/34_es
new file mode 100644
index 0000000..a6efe20
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/34_es
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/43_de b/java/src/com/google/i18n/phonenumbers/geocoding/data/43_de
new file mode 100644
index 0000000..270c2c9
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/43_de
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/44_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/44_en
new file mode 100644
index 0000000..b19defd
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/44_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/46_sv b/java/src/com/google/i18n/phonenumbers/geocoding/data/46_sv
new file mode 100644
index 0000000..c566c43
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/46_sv
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/49_de b/java/src/com/google/i18n/phonenumbers/geocoding/data/49_de
new file mode 100644
index 0000000..87926bd
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/49_de
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/54_es b/java/src/com/google/i18n/phonenumbers/geocoding/data/54_es
new file mode 100644
index 0000000..d4df906
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/54_es
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/55_pt b/java/src/com/google/i18n/phonenumbers/geocoding/data/55_pt
new file mode 100644
index 0000000..3cab21f
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/55_pt
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/56_es b/java/src/com/google/i18n/phonenumbers/geocoding/data/56_es
new file mode 100644
index 0000000..f36465f
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/56_es
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_en
new file mode 100644
index 0000000..964e026
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_ko b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_ko
new file mode 100644
index 0000000..69c8afe
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_ko
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh
new file mode 100644
index 0000000..eccb8a7
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh_Hant b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh_Hant
new file mode 100644
index 0000000..e677541
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh_Hant
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/86_zh b/java/src/com/google/i18n/phonenumbers/geocoding/data/86_zh
new file mode 100644
index 0000000..82239cd
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/86_zh
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/config b/java/src/com/google/i18n/phonenumbers/geocoding/data/config
new file mode 100644
index 0000000..ab7f881
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/config
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java b/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java
index b03f6e5..183dc6d 100644
--- a/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java
+++ b/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java
@@ -56,6 +56,28 @@
     assertEquals("650253", formatter.inputDigit('3'));
   }
 
+  public void testTooLongNumberMatchingMultipleLeadingDigits() {
+    // See http://code.google.com/p/libphonenumber/issues/detail?id=36
+    // The bug occurred last time for countries which have two formatting rules with exactly the
+    // same leading digits pattern but differ in length.
+    AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("ZZ");
+    assertEquals("+", formatter.inputDigit('+'));
+    assertEquals("+8", formatter.inputDigit('8'));
+    assertEquals("+81 ", formatter.inputDigit('1'));
+    assertEquals("+81 9", formatter.inputDigit('9'));
+    assertEquals("+81 90", formatter.inputDigit('0'));
+    assertEquals("+81 90 1", formatter.inputDigit('1'));
+    assertEquals("+81 90 12", formatter.inputDigit('2'));
+    assertEquals("+81 90 123", formatter.inputDigit('3'));
+    assertEquals("+81 90 1234", formatter.inputDigit('4'));
+    assertEquals("+81 90 1234 5", formatter.inputDigit('5'));
+    assertEquals("+81 90 1234 56", formatter.inputDigit('6'));
+    assertEquals("+81 90 1234 567", formatter.inputDigit('7'));
+    assertEquals("+81 90 1234 5678", formatter.inputDigit('8'));
+    assertEquals("+81 90 12 345 6789", formatter.inputDigit('9'));
+    assertEquals("+81901234567890", formatter.inputDigit('0'));
+  }
+
   public void testAYTFUS() {
     AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("US");
     assertEquals("6", formatter.inputDigit('6'));
@@ -343,7 +365,7 @@
   }
 
   public void testAYTFGBTollFree() {
-    AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("gb");
+    AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("GB");
     assertEquals("0", formatter.inputDigit('0'));
     assertEquals("08", formatter.inputDigit('8'));
     assertEquals("080", formatter.inputDigit('0'));
diff --git a/java/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java b/java/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java
index 45ef033..45d352b 100644
--- a/java/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java
+++ b/java/test/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMapForTesting.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2011 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -12,9 +12,10 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
- * This file is automatically generated by BuildMetadataProtoFromXml. Please
- * don't modify directly.
+ */
+
+/* This file is automatically generated by {@link BuildMetadataProtoFromXml}.
+ * Please don't modify it directly.
  */
 
 package com.google.i18n.phonenumbers;
diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java
index 0c55964..1f4d5fb 100644
--- a/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java
+++ b/java/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java
@@ -221,6 +221,123 @@
     assertEquals(number, matchWithSpaces.rawString());
   }
 
+  public void testIsLatinLetter() throws Exception {
+    assertTrue(PhoneNumberMatcher.isLatinLetter('c'));
+    assertTrue(PhoneNumberMatcher.isLatinLetter('C'));
+    assertTrue(PhoneNumberMatcher.isLatinLetter('\u00C9'));
+    assertTrue(PhoneNumberMatcher.isLatinLetter('\u0301'));  // Combining acute accent
+    // Punctuation, digits and white-space are not considered "latin letters".
+    assertFalse(PhoneNumberMatcher.isLatinLetter(':'));
+    assertFalse(PhoneNumberMatcher.isLatinLetter('5'));
+    assertFalse(PhoneNumberMatcher.isLatinLetter('-'));
+    assertFalse(PhoneNumberMatcher.isLatinLetter('.'));
+    assertFalse(PhoneNumberMatcher.isLatinLetter(' '));
+    assertFalse(PhoneNumberMatcher.isLatinLetter('\u6211'));  // Chinese character
+  }
+
+  public void testMatchesWithSurroundingLatinChars() throws Exception {
+    ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(5);
+    contextPairs.add(new NumberContext("abc", "def"));
+    contextPairs.add(new NumberContext("abc", ""));
+    contextPairs.add(new NumberContext("", "def"));
+    // Latin small letter e with an acute accent.
+    contextPairs.add(new NumberContext("\u00C9", ""));
+    // Same character decomposed (with combining mark).
+    contextPairs.add(new NumberContext("e\u0301", ""));
+
+    // Numbers should not be considered valid, if they are surrounded by Latin characters, but
+    // should be considered possible.
+    findMatchesInContexts(contextPairs, false, true);
+  }
+
+  public void testMatchesWithSurroundingLatinCharsAndLeadingPunctuation() throws Exception {
+    // Contexts with trailing characters. Leading characters are okay here since the numbers we will
+    // insert start with punctuation, but trailing characters are still not allowed.
+    ArrayList<NumberContext> possibleOnlyContexts = new ArrayList<NumberContext>(3);
+    possibleOnlyContexts.add(new NumberContext("abc", "def"));
+    possibleOnlyContexts.add(new NumberContext("", "def"));
+    possibleOnlyContexts.add(new NumberContext("", "\u00C9"));
+
+    // Numbers should not be considered valid, if they have trailing Latin characters, but should be
+    // considered possible.
+    String numberWithPlus = "+14156667777";
+    String numberWithBrackets = "(415)6667777";
+    findMatchesInContexts(possibleOnlyContexts, false, true, "US", numberWithPlus);
+    findMatchesInContexts(possibleOnlyContexts, false, true, "US", numberWithBrackets);
+
+    ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(4);
+    validContexts.add(new NumberContext("abc", ""));
+    validContexts.add(new NumberContext("\u00C9", ""));
+    validContexts.add(new NumberContext("\u00C9", "."));  // Trailing punctuation.
+    validContexts.add(new NumberContext("\u00C9", " def"));  // Trailing white-space.
+
+    // Numbers should be considered valid, since they start with punctuation.
+    findMatchesInContexts(validContexts, true, true, "US", numberWithPlus);
+    findMatchesInContexts(validContexts, true, true, "US", numberWithBrackets);
+  }
+
+  public void testMatchesWithSurroundingChineseChars() throws Exception {
+    ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(3);
+    validContexts.add(new NumberContext("\u6211\u7684\u7535\u8BDD\u53F7\u7801\u662F", ""));
+    validContexts.add(new NumberContext("", "\u662F\u6211\u7684\u7535\u8BDD\u53F7\u7801"));
+    validContexts.add(new NumberContext("\u8BF7\u62E8\u6253", "\u6211\u5728\u660E\u5929"));
+
+    // Numbers should be considered valid, since they are surrounded by Chinese.
+    findMatchesInContexts(validContexts, true, true);
+  }
+
+  public void testMatchesWithSurroundingPunctuation() throws Exception {
+    ArrayList<NumberContext> validContexts = new ArrayList<NumberContext>(4);
+    validContexts.add(new NumberContext("My number-", ""));  // At end of text.
+    validContexts.add(new NumberContext("", ".Nice day."));  // At start of text.
+    validContexts.add(new NumberContext("Tel:", "."));  // Punctuation surrounds number.
+    validContexts.add(new NumberContext("Tel: ", " on Saturdays."));  // White-space is also fine.
+
+    // Numbers should be considered valid, since they are surrounded by punctuation.
+    findMatchesInContexts(validContexts, true, true);
+  }
+
+  /**
+   * Helper method which tests the contexts provided and ensures that:
+   * -- if isValid is true, they all find a test number inserted in the middle when leniency of
+   *  matching is set to VALID; else no test number should be extracted at that leniency level
+   * -- if isPossible is true, they all find a test number inserted in the middle when leniency of
+   *  matching is set to POSSIBLE; else no test number should be extracted at that leniency level
+   */
+  private void findMatchesInContexts(List<NumberContext> contexts, boolean isValid,
+                                     boolean isPossible, String region, String number) {
+    if (isValid) {
+      doTestInContext(number, region, contexts, Leniency.VALID);
+    } else {
+      for (NumberContext context : contexts) {
+        String text = context.leadingText + number + context.trailingText;
+        assertTrue("Should not have found a number in " + text,
+                   hasNoMatches(phoneUtil.findNumbers(text, region)));
+      }
+    }
+    if (isPossible) {
+      doTestInContext(number, region, contexts, Leniency.POSSIBLE);
+    } else {
+      for (NumberContext context : contexts) {
+        String text = context.leadingText + number + context.trailingText;
+        assertTrue("Should not have found a number in " + text,
+                   hasNoMatches(phoneUtil.findNumbers(text, region, Leniency.POSSIBLE,
+                                                      Long.MAX_VALUE)));
+      }
+    }
+  }
+
+  /**
+   * Variant of findMatchesInContexts that uses a default number and region.
+   */
+  private void findMatchesInContexts(List<NumberContext> contexts, boolean isValid,
+                                     boolean isPossible) {
+    String region = "US";
+    String number = "415-666-7777";
+
+    findMatchesInContexts(contexts, isValid, isPossible, region, number);
+  }
+
   public void testNonMatchingBracketsAreInvalid() throws Exception {
     // The digits up to the ", " form a valid US number, but it shouldn't be matched as one since
     // there was a non-matching bracket present.
@@ -473,6 +590,9 @@
     }
   }
 
+  /**
+   * Tests valid numbers in contexts that should pass for {@link Leniency#POSSIBLE}.
+   */
   private void findPossibleInContext(String number, String defaultCountry) {
     ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(15);
     contextPairs.add(new NumberContext("", ""));  // no context
@@ -513,7 +633,8 @@
   }
 
   /**
-   * Tests valid numbers in contexts that fail for {@link Leniency#POSSIBLE}.
+   * Tests valid numbers in contexts that fail for {@link Leniency#POSSIBLE} but are valid for
+   * {@link Leniency#VALID}.
    */
   private void findValidInContext(String number, String defaultCountry) {
     ArrayList<NumberContext> contextPairs = new ArrayList<NumberContext>(5);
diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberOfflineGeocoderTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberOfflineGeocoderTest.java
deleted file mode 100644
index facd3c4..0000000
--- a/java/test/com/google/i18n/phonenumbers/PhoneNumberOfflineGeocoderTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.i18n.phonenumbers;
-
-import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
-import junit.framework.TestCase;
-
-import java.util.Locale;
-
-/**
- * Unit tests for PhoneNumberOfflineGeocoder.java
- *
- * @author Shaopeng Jia
- */
-public class PhoneNumberOfflineGeocoderTest extends TestCase {
-  private PhoneNumberOfflineGeocoder geocoder;
-  static final String TEST_META_DATA_FILE_PREFIX =
-      "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting";
-
-  // Set up some test numbers to re-use.
-  private static final PhoneNumber US_NUMBER1 =
-      new PhoneNumber().setCountryCode(1).setNationalNumber(6502530000L);
-  private static final PhoneNumber BS_NUMBER1 =
-      new PhoneNumber().setCountryCode(1).setNationalNumber(2423651234L);
-  private static final PhoneNumber AU_NUMBER =
-      new PhoneNumber().setCountryCode(61).setNationalNumber(236618300L);
-  private static final PhoneNumber NUMBER_WITH_INVALID_COUNTRY_CODE =
-      new PhoneNumber().setCountryCode(999).setNationalNumber(2423651234L);
-
-  public PhoneNumberOfflineGeocoderTest() {
-    PhoneNumberUtil.resetInstance();
-    PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(
-        TEST_META_DATA_FILE_PREFIX,
-        CountryCodeToRegionCodeMapForTesting.getCountryCodeToRegionCodeMap());
-    geocoder = new PhoneNumberOfflineGeocoder(phoneUtil);
-  }
-
-  public void testGetCompactDescriptionForNumber() {
-    assertEquals("United States",
-        geocoder.getDescriptionForNumber(US_NUMBER1, Locale.ENGLISH));
-    assertEquals("Stati Uniti",
-        geocoder.getDescriptionForNumber(US_NUMBER1, Locale.ITALIAN));
-    assertEquals("Bahamas",
-        geocoder.getDescriptionForNumber(BS_NUMBER1, Locale.ENGLISH));
-    assertEquals("Australia",
-        geocoder.getDescriptionForNumber(AU_NUMBER, Locale.ENGLISH));
-    assertEquals("", geocoder.getDescriptionForNumber(NUMBER_WITH_INVALID_COUNTRY_CODE,
-                                                      Locale.ENGLISH));
-  }
-}
diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
index f4a2751..2fff6f5 100644
--- a/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
+++ b/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
@@ -145,8 +145,8 @@
     assertTrue(metadata.hasNationalPrefix());
     assertEquals(2, metadata.numberFormatSize());
     assertEquals("(\\d{3})(\\d{3})(\\d{4})",
-                 metadata.getNumberFormat(0).getPattern());
-    assertEquals("$1 $2 $3", metadata.getNumberFormat(0).getFormat());
+                 metadata.getNumberFormat(1).getPattern());
+    assertEquals("$1 $2 $3", metadata.getNumberFormat(1).getFormat());
     assertEquals("[13-9]\\d{9}|2[0-35-9]\\d{8}",
                  metadata.getGeneralDesc().getNationalNumberPattern());
     assertEquals("\\d{7}(?:\\d{3})?", metadata.getGeneralDesc().getPossibleNumberPattern());
@@ -290,6 +290,13 @@
                                                  PhoneNumberUtil.PhoneNumberType.MOBILE));
   }
 
+  public void testConvertAlphaCharactersInNumber() {
+    String input = "1800-ABC-DEF";
+    // Alpha chars are converted to digits; everything else is left untouched.
+    String expectedOutput = "1800-222-333";
+    assertEquals(expectedOutput, PhoneNumberUtil.convertAlphaCharactersInNumber(input));
+  }
+
   public void testNormaliseRemovePunctuation() {
     String inputNumber = "034-56&+#234";
     String expectedOutput = "03456234";
@@ -813,7 +820,6 @@
     // This number is valid for the Bahamas, but is not a valid US number.
     assertTrue(phoneUtil.isValidNumber(BS_NUMBER));
     assertTrue(phoneUtil.isValidNumberForRegion(BS_NUMBER, RegionCode.BS));
-    assertTrue(phoneUtil.isValidNumberForRegion(BS_NUMBER, "bs"));
     assertFalse(phoneUtil.isValidNumberForRegion(BS_NUMBER, RegionCode.US));
     PhoneNumber bsInvalidNumber =
         new PhoneNumber().setCountryCode(1).setNationalNumber(2421232345L);
@@ -878,7 +884,6 @@
   public void testGetCountryCodeForRegion() {
     assertEquals(1, phoneUtil.getCountryCodeForRegion(RegionCode.US));
     assertEquals(64, phoneUtil.getCountryCodeForRegion(RegionCode.NZ));
-    assertEquals(64, phoneUtil.getCountryCodeForRegion("nz"));
     assertEquals(0, phoneUtil.getCountryCodeForRegion(null));
     assertEquals(0, phoneUtil.getCountryCodeForRegion(RegionCode.ZZ));
     // CS is already deprecated so the library doesn't support it.
@@ -904,7 +909,6 @@
   public void testIsNANPACountry() {
     assertTrue(phoneUtil.isNANPACountry(RegionCode.US));
     assertTrue(phoneUtil.isNANPACountry(RegionCode.BS));
-    assertTrue(phoneUtil.isNANPACountry("bs"));
   }
 
   public void testIsPossibleNumber() {
@@ -921,7 +925,6 @@
     assertTrue(phoneUtil.isPossibleNumber("(020) 7031 3000", RegionCode.GB));
     assertTrue(phoneUtil.isPossibleNumber("7031 3000", RegionCode.GB));
     assertTrue(phoneUtil.isPossibleNumber("3331 6005", RegionCode.NZ));
-    assertTrue(phoneUtil.isPossibleNumber("3331 6005", "nz"));
   }
 
   public void testIsPossibleNumberWithReason() {
@@ -1037,6 +1040,9 @@
     // Alpha numbers.
     assertTrue(PhoneNumberUtil.isViablePhoneNumber("0800-4-pizza"));
     assertTrue(PhoneNumberUtil.isViablePhoneNumber("0800-4-PIZZA"));
+  }
+
+  public void testIsViablePhoneNumberNonAscii() {
     // Only one or two digits before possible punctuation followed by more digits.
     assertTrue(PhoneNumberUtil.isViablePhoneNumber("1\u300034"));
     assertFalse(PhoneNumberUtil.isViablePhoneNumber("1\u30003+4"));
@@ -1296,7 +1302,6 @@
   public void testParseNationalNumber() throws Exception {
     // National prefix attached.
     assertEquals(NZ_NUMBER, phoneUtil.parse("033316005", RegionCode.NZ));
-    assertEquals(NZ_NUMBER, phoneUtil.parse("033316005", "nz"));
     assertEquals(NZ_NUMBER, phoneUtil.parse("33316005", RegionCode.NZ));
     // National prefix attached and some formatting present.
     assertEquals(NZ_NUMBER, phoneUtil.parse("03-331 6005", RegionCode.NZ));
@@ -1352,6 +1357,9 @@
     assertEquals(US_NUMBER, phoneUtil.parse("0~01-650-253-0000", RegionCode.PL));
     // Using "++" at the start.
     assertEquals(US_NUMBER, phoneUtil.parse("++1 (650) 253-0000", RegionCode.PL));
+  }
+
+  public void testParseNonAscii() throws Exception {
     // Using a full-width plus sign.
     assertEquals(US_NUMBER, phoneUtil.parse("\uFF0B1 (650) 253-0000", RegionCode.SG));
     // The whole number, including punctuation, is here represented in full-width form.
@@ -1364,6 +1372,11 @@
                                             "\u3000\uFF12\uFF15\uFF13\u30FC\uFF10\uFF10\uFF10" +
                                             "\uFF10",
                                             RegionCode.SG));
+
+    // Using a very strange decimal digit range (Mongolian digits).
+    assertEquals(US_NUMBER, phoneUtil.parse("\u1811 \u1816\u1815\u1810 " +
+                                            "\u1812\u1815\u1813 \u1810\u1810\u1810\u1810",
+                                            RegionCode.US));
   }
 
   public void testParseWithLeadingZero() throws Exception {
diff --git a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_JP b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_JP
index f1c9af5..cfb8f71 100644
--- a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_JP
+++ b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_JP
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_US b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_US
index 9e6ba69..d93e0a2 100644
--- a/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_US
+++ b/java/test/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting_US
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java b/java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java
new file mode 100644
index 0000000..07b9451
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.geocoding;
+
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Unittests for AreaCodeMap.java
+ *
+ * @author Shaopeng Jia
+ */
+public class AreaCodeMapTest extends TestCase {
+  private final AreaCodeMap areaCodeMap = new AreaCodeMap();
+  private PhoneNumber number = new PhoneNumber();
+  private static final Logger LOGGER = Logger.getLogger(AreaCodeMapTest.class.getName());
+  static final String TEST_META_DATA_FILE_PREFIX =
+      "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting";
+
+  public AreaCodeMapTest() {
+    SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
+    sortedMap.put(1212, "New York");
+    sortedMap.put(1480, "Arizona");
+    sortedMap.put(1650, "California");
+    sortedMap.put(1907, "Alaska");
+    sortedMap.put(1201664, "Westwood, NJ");
+    sortedMap.put(1480893, "Phoenix, AZ");
+    sortedMap.put(1501372, "Little Rock, AR");
+    sortedMap.put(1626308, "Alhambra, CA");
+    sortedMap.put(1650345, "San Mateo, CA");
+    sortedMap.put(1867993, "Dawson, YT");
+    sortedMap.put(1972480, "Richardson, TX");
+
+    areaCodeMap.readAreaCodeMap(sortedMap);
+  }
+
+  public void testLookupInvalidNumber_US() {
+    // central office code cannot start with 1.
+    number.setCountryCode(1).setNationalNumber(2121234567L);
+    assertEquals("New York", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumber_NJ() {
+    number.setCountryCode(1).setNationalNumber(2016641234L);
+    assertEquals("Westwood, NJ", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumber_NY() {
+    number.setCountryCode(1).setNationalNumber(2126641234L);
+    assertEquals("New York", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumber_CA_1() {
+    number.setCountryCode(1).setNationalNumber(6503451234L);
+    assertEquals("San Mateo, CA", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumber_CA_2() {
+    number.setCountryCode(1).setNationalNumber(6502531234L);
+    assertEquals("California", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumberFound_TX() {
+    number.setCountryCode(1).setNationalNumber(9724801234L);
+    assertEquals("Richardson, TX", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumberNotFound_TX() {
+    number.setCountryCode(1).setNationalNumber(9724811234L);
+    assertEquals("", areaCodeMap.lookup(number));
+  }
+
+  public void testLookupNumber_CH() {
+    number.setCountryCode(41).setNationalNumber(446681300L);
+    assertEquals("", areaCodeMap.lookup(number));
+  }
+
+  public void testReadWriteExternal() {
+    try {
+      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+      ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+      areaCodeMap.writeExternal(objectOutputStream);
+      objectOutputStream.flush();
+
+      AreaCodeMap newAreaCodeMap = new AreaCodeMap();
+      newAreaCodeMap.readExternal(
+          new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
+
+      assertEquals(areaCodeMap.toString(), newAreaCodeMap.toString());
+    } catch (IOException e) {
+      LOGGER.log(Level.SEVERE, e.getMessage());
+      fail();
+    }
+  }
+}
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/MappingFileProviderTest.java b/java/test/com/google/i18n/phonenumbers/geocoding/MappingFileProviderTest.java
new file mode 100644
index 0000000..83f4f54
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/MappingFileProviderTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.geocoding;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Unittests for MappingFileProvider.java
+ *
+ * @author Shaopeng Jia
+ */
+public class MappingFileProviderTest extends TestCase {
+  private final MappingFileProvider mappingProvider = new MappingFileProvider();
+  private static final Logger LOGGER = Logger.getLogger(MappingFileProviderTest.class.getName());
+
+  public MappingFileProviderTest() {
+    SortedMap<Integer, Set<String>> mapping = new TreeMap<Integer, Set<String>>();
+    mapping.put(1, newHashSet("en"));
+    mapping.put(86, newHashSet("zh", "en", "zh_Hant"));
+    mapping.put(41, newHashSet("de", "fr", "it", "rm"));
+    mapping.put(65, newHashSet("en", "zh_Hans", "ms", "ta"));
+
+    mappingProvider.readFileConfigs(mapping);
+  }
+
+  private static HashSet<String> newHashSet(String... strings) {
+    HashSet<String> set = new HashSet<String>();
+    set.addAll(Arrays.asList(strings));
+    return set;
+  }
+
+  public void testReadWriteExternal() {
+    try {
+      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+      ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+      mappingProvider.writeExternal(objectOutputStream);
+      objectOutputStream.flush();
+
+      MappingFileProvider newMappingProvider = new MappingFileProvider();
+      newMappingProvider.readExternal(
+          new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
+      assertEquals(mappingProvider.toString(), newMappingProvider.toString());
+    } catch (IOException e) {
+      LOGGER.log(Level.SEVERE, e.getMessage());
+      fail();
+    }
+  }
+
+  public void testGetFileName() {
+    assertEquals("1_en", mappingProvider.getFileName(1, "en", "", ""));
+    assertEquals("1_en", mappingProvider.getFileName(1, "en", "", "US"));
+    assertEquals("1_en", mappingProvider.getFileName(1, "en", "", "GB"));
+    assertEquals("41_de", mappingProvider.getFileName(41, "de", "", "CH"));
+    assertEquals("", mappingProvider.getFileName(44, "en", "", "GB"));
+    assertEquals("86_zh", mappingProvider.getFileName(86, "zh", "", ""));
+    assertEquals("86_zh", mappingProvider.getFileName(86, "zh", "Hans", ""));
+    assertEquals("86_zh", mappingProvider.getFileName(86, "zh", "", "CN"));
+    assertEquals("", mappingProvider.getFileName(86, "", "", "CN"));
+    assertEquals("86_zh", mappingProvider.getFileName(86, "zh", "Hans", "CN"));
+    assertEquals("86_zh", mappingProvider.getFileName(86, "zh", "Hans", "SG"));
+    assertEquals("86_zh", mappingProvider.getFileName(86, "zh", "", "SG"));
+    assertEquals("86_zh_Hant", mappingProvider.getFileName(86, "zh", "", "TW"));
+    assertEquals("86_zh_Hant", mappingProvider.getFileName(86, "zh", "", "HK"));
+    assertEquals("86_zh_Hant", mappingProvider.getFileName(86, "zh", "Hant", "TW"));
+  }
+}
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java b/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
new file mode 100644
index 0000000..39fd787
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.geocoding;
+
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+/**
+ * Unit tests for PhoneNumberOfflineGeocoder.java
+ *
+ * @author Shaopeng Jia
+ */
+public class PhoneNumberOfflineGeocoderTest extends TestCase {
+  private final PhoneNumberOfflineGeocoder geocoder =
+      new PhoneNumberOfflineGeocoder(TEST_MAPPING_DATA_DIRECTORY);
+  static final String TEST_META_DATA_FILE_PREFIX =
+      "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting";
+  private static final String TEST_MAPPING_DATA_DIRECTORY =
+      "/com/google/i18n/phonenumbers/geocoding/testing_data/";
+
+  // Set up some test numbers to re-use.
+  private static final PhoneNumber KO_NUMBER1 =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(22123456L);
+  private static final PhoneNumber KO_NUMBER2 =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(322123456L);
+  private static final PhoneNumber KO_NUMBER3 =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(6421234567L);
+  private static final PhoneNumber US_NUMBER1 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6502530000L);
+  private static final PhoneNumber US_NUMBER2 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6509600000L);
+  private static final PhoneNumber US_NUMBER3 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(2128120000L);
+  private static final PhoneNumber BS_NUMBER1 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(2423651234L);
+  private static final PhoneNumber AU_NUMBER =
+      new PhoneNumber().setCountryCode(61).setNationalNumber(236618300L);
+  private static final PhoneNumber NUMBER_WITH_INVALID_COUNTRY_CODE =
+      new PhoneNumber().setCountryCode(999).setNationalNumber(2423651234L);
+
+  public void testGetDescriptionForNumberWithNoDataFile() {
+    // No data file containing mappings for US numbers is available in Chinese for the unittests. As
+    // a result, the country name of United States in simplified Chinese is returned.
+    assertEquals("\u7F8E\u56FD",
+        geocoder.getDescriptionForNumber(US_NUMBER1, Locale.SIMPLIFIED_CHINESE));
+    assertEquals("Stati Uniti",
+        geocoder.getDescriptionForNumber(US_NUMBER1, Locale.ITALIAN));
+    assertEquals("Bahamas",
+        geocoder.getDescriptionForNumber(BS_NUMBER1, new Locale("en", "US")));
+    assertEquals("Australia",
+        geocoder.getDescriptionForNumber(AU_NUMBER, new Locale("en", "US")));
+    assertEquals("", geocoder.getDescriptionForNumber(NUMBER_WITH_INVALID_COUNTRY_CODE,
+                                                      new Locale("en", "US")));
+  }
+
+  public void testGetDescriptionForNumber_en_US() {
+    assertEquals("CA",
+        geocoder.getDescriptionForNumber(US_NUMBER1, new Locale("en", "US")));
+    assertEquals("Mountain View, CA",
+        geocoder.getDescriptionForNumber(US_NUMBER2, new Locale("en", "US")));
+    assertEquals("New York, NY",
+        geocoder.getDescriptionForNumber(US_NUMBER3, new Locale("en", "US")));
+  }
+
+  public void testGetDescriptionForKoreanNumber() {
+    assertEquals("Seoul",
+        geocoder.getDescriptionForNumber(KO_NUMBER1, Locale.ENGLISH));
+    assertEquals("Incheon",
+        geocoder.getDescriptionForNumber(KO_NUMBER2, Locale.ENGLISH));
+    assertEquals("Jeju",
+        geocoder.getDescriptionForNumber(KO_NUMBER3, Locale.ENGLISH));
+    assertEquals("\uC11C\uC6B8",
+        geocoder.getDescriptionForNumber(KO_NUMBER1, Locale.KOREAN));
+    assertEquals("\uC778\uCC9C",
+        geocoder.getDescriptionForNumber(KO_NUMBER2, Locale.KOREAN));
+    assertEquals("\uC81C\uC8FC",
+        geocoder.getDescriptionForNumber(KO_NUMBER3, Locale.KOREAN));
+  }
+}
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/1_en b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/1_en
new file mode 100644
index 0000000..6ef7841
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/1_en
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_en b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_en
new file mode 100644
index 0000000..964e026
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_en
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_ko b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_ko
new file mode 100644
index 0000000..69c8afe
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_ko
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/86_en b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/86_en
new file mode 100644
index 0000000..710e1e7
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/86_en
Binary files differ
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/config b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/config
new file mode 100644
index 0000000..ab7bf47
--- /dev/null
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/config
Binary files differ