Update libphonenumber to 3.7.1

Bug: 4939002,4989129
Change-Id: If8ba75b91380a0280793bec13000806d72fa2658
diff --git a/README.android b/README.android
index 63b7eac..5779273 100644
--- a/README.android
+++ b/README.android
@@ -1,4 +1,4 @@
 URL: http://code.google.com/p/libphonenumber/
-Version: 3.7 (r278)
+Version: 3.7.1 (r294)
 License: Apache 2
 Description: Google Phone Number Library.
diff --git a/java/build.xml b/java/build.xml
deleted file mode 100644
index 63398b5..0000000
--- a/java/build.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-<?xml version="1.0" ?>
-
-<project name="libphonenumber" default="compile">
-  <property name="src.dir" value="src"/>
-  <property name="test.dir" value="test"/>
-  <property name="build.dir" value="build"/>
-  <property name="classes.dir" value="${build.dir}/classes"/>
-  <property name="jar.dir" value="${build.dir}/jar"/>
-  <property name="lib.dir" value="lib"/>
-  <property name="report.dir" value="${build.dir}/junitreport"/>
-
-  <path id="classpath">
-    <fileset dir="${lib.dir}" includes="**/*.jar"/>
-  </path>
-  <path id="test.classpath">
-    <pathelement location="${classes.dir}"/>
-    <pathelement location="lib/junit/junit-4.8.1.jar"/>
-    <pathelement location="${jar.dir}/${ant.project.name}-test.jar"/>
-    <fileset dir="${lib.dir}">
-      <include name="**/*.jar"/>
-    </fileset>
-  </path>
-
-  <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"/>
-  </target>
-
-  <target name="jar" depends="compile">
-    <mkdir dir="${jar.dir}"/>
-    <jar destfile="${jar.dir}/${ant.project.name}.jar">
-      <fileset dir="${classes.dir}">
-        <include name="**/*.class"/>
-        <exclude name="**/*Test*"/>
-        <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,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*"/>
-      </fileset>
-      <fileset dir="${src.dir}">
-        <include name="**/PhoneNumberMetadataProto*"/>
-      </fileset>
-      <fileset dir="${test.dir}">
-        <include name="**/PhoneNumberMetadataProtoForTesting*"/>
-      </fileset>
-      <fileset dir="${test.dir}">
-        <include name="**/geocoding/testing_data/*"/>
-      </fileset>
-    </jar>
-  </target>
-
-  <target name="junit" depends="test-jar">
-    <mkdir dir="${report.dir}"/>
-    <junit printsummary="yes">
-      <classpath refid="test.classpath"/>
-      <formatter type="xml"/>
-      <batchtest fork="no" todir="${report.dir}">
-        <fileset dir="${test.dir}" includes="**/*Test.java"/>
-      </batchtest>
-    </junit>
-  </target>
-
-  <target name="junitreport">
-    <junitreport todir="${report.dir}">
-      <fileset dir="${report.dir}" includes="TEST-*.xml"/>
-        <report todir="${report.dir}"/>
-    </junitreport>
-  </target>
-
-  <target name="clean" description="Remove generated files.">
-    <delete dir="${build.dir}"/>
-  </target>
-
-  <target name="clean-build" depends="clean,jar"/>
-</project>
-
diff --git a/java/pom.xml b/java/pom.xml
deleted file mode 100644
index e376262..0000000
--- a/java/pom.xml
+++ /dev/null
@@ -1,286 +0,0 @@
-<?xml version="1.0"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>com.googlecode.libphonenumber</groupId>
-  <artifactId>libphonenumber</artifactId>
-  <version>3.7-SNAPSHOT</version>
-  <packaging>jar</packaging>
-  <name>libphonenumber</name>
-  <url>http://code.google.com/p/libphonenumber/</url>
-
-  <parent>
-    <groupId>org.sonatype.oss</groupId>
-    <artifactId>oss-parent</artifactId>
-    <version>7</version>
-  </parent>
-
-  <description>
-    Google's common Java library for parsing, formatting, storing and validating international phone numbers.
-    Optimized for running on smartphones.
-  </description>
-
-  <organization>
-    <name>Google</name>
-    <url>http://www.google.com/</url>
-  </organization>
-
-  <licenses>
-    <license>
-      <name>The Apache Software License, Version 2.0</name>
-      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
-    </license>
-  </licenses>
-
-  <scm>
-    <connection>scm:svn:http://libphonenumber.googlecode.com/svn/trunk/java/</connection>
-    <developerConnection>scm:svn:https://libphonenumber.googlecode.com/svn/trunk/java/</developerConnection>
-    <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>
-      <name>Shaopeng Jia</name>
-      <email>jia.shao.peng@gmail.com</email>
-      <organization>Google</organization>
-      <roles>
-        <role>owner</role>
-        <role>developer</role>
-      </roles>
-    </developer>
-    <developer>
-      <id>lararennie</id>
-      <name>Lara Rennie</name>
-      <email>lararennie@google.com</email>
-      <organization>Google</organization>
-      <roles>
-        <role>developer</role>
-      </roles>
-    </developer>
-  </developers>
-
-  <contributors>
-    <contributor>
-      <name>tronikos</name>
-      <email>tronikos@gmail.com</email>
-    </contributor>
-    <contributor>
-      <name>g1smd.email</name>
-      <email>g1smd.email@gmail.com</email>
-    </contributor>
-    <contributor>
-      <name>Philippe Liard</name>
-      <email>philip.liard@gmail.com</email>
-    </contributor>
-  </contributors>
-
-  <build>
-    <sourceDirectory>src</sourceDirectory>
-    <testSourceDirectory>test</testSourceDirectory>
-    <resources>
-      <resource>
-        <directory>src/com/google/i18n/phonenumbers/data</directory>
-        <targetPath>com/google/i18n/phonenumbers/data</targetPath>
-      </resource>
-    </resources>
-    <testResources>
-      <testResource>
-        <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>
-          <execution>
-            <id>attach-sources</id>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.7</version>
-        <executions>
-          <execution>
-            <id>attach-javadocs</id>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <artifactId>maven-release-plugin</artifactId>
-        <version>2.0-beta-7</version>
-        <configuration>
-          <tagBase>
-            https://libphonenumber.googlecode.com/svn/tags/
-          </tagBase>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>2.3.2</version>
-        <configuration>
-          <source>1.5</source>
-          <target>1.5</target>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-
-  <profiles>
-    <profile>
-      <id>release-sign-artifacts</id>
-      <activation>
-        <property>
-          <name>performRelease</name>
-          <value>true</value>
-        </property>
-      </activation>
-      <build>
-        <plugins>
-          <plugin>
-            <groupId>org.apache.maven.plugins</groupId>
-            <artifactId>maven-gpg-plugin</artifactId>
-            <version>1.1</version>
-            <executions>
-              <execution>
-                <id>sign-artifacts</id>
-                <phase>verify</phase>
-                <goals>
-                  <goal>sign</goal>
-                </goals>
-              </execution>
-            </executions>
-          </plugin>
-        </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>
-
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.8.1</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-</project>
diff --git a/java/release_notes.txt b/java/release_notes.txt
index 2fba846..cbcb8a5 100644
--- a/java/release_notes.txt
+++ b/java/release_notes.txt
@@ -1,3 +1,12 @@
+July 5th, 2011
+* Code changes
+ - Refactored AreaCodeMap to minimize binary and memory footprint by using 2 different strategies.
+ - Refactored BuildMetadataFromXml.java and added unittests.
+
+* Metadata changes
+ - Regenerate binaries for all existing area code mapping data with smaller sizes.
+ - Added city-level area code data mapping for US and Canada.
+
 June 29th, 2011
 * Code changes
  - Fixed issue 38, issue 39, issue 41 and issue 43
diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java b/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
index ac8e103..da57a13 100644
--- a/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
+++ b/java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
@@ -1596,11 +1596,11 @@
   }
 
   /**
-   * Checks whether countryCode represents the country calling code from a region whose national
-   * significant number could contain a leading zero. An example of such a region is Italy. Returns
-   * false if no metadata for the country is found.
+   * Checks whether the country calling code is from a region whose national significant number
+   * could contain a leading zero. An example of such a region is Italy. Returns false if no
+   * metadata for the country is found.
    */
-  boolean isLeadingZeroPossible(int countryCallingCode) {
+  public boolean isLeadingZeroPossible(int countryCallingCode) {
     PhoneMetadata mainMetadataForCallingCode = getMetadataForRegion(
         getRegionCodeForCountryCode(countryCallingCode));
     if (mainMetadataForCallingCode == null) {
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java b/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
index 644c14b..8b746e3 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMap.java
@@ -19,13 +19,15 @@
 import com.google.i18n.phonenumbers.PhoneNumberUtil;
 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
 
+import java.io.ByteArrayOutputStream;
 import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
 import java.util.SortedMap;
 import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.logging.Logger;
 
 /**
  * A utility that maps phone number prefixes to a string describing the geographical area the prefix
@@ -34,73 +36,109 @@
  * @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 int countryCallingCode;
+  private final boolean isLeadingZeroPossible;
   private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
+  private static final Logger LOGGER = Logger.getLogger(AreaCodeMap.class.getName());
+
+  private AreaCodeMapStorageStrategy areaCodeMapStorage;
+
+  // @VisibleForTesting
+  AreaCodeMapStorageStrategy getAreaCodeMapStorage() {
+    return areaCodeMapStorage;
+  }
 
   /**
    * 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)}.
+   *
+   * @param countryCallingCode  the country calling code for the region that the area code map
+   *     belongs to.
    */
-  public AreaCodeMap() {}
+  public AreaCodeMap(int countryCallingCode) {
+    this.countryCallingCode = countryCallingCode;
+    isLeadingZeroPossible = phoneUtil.isLeadingZeroPossible(countryCallingCode);
+  }
 
   /**
-   * Creates an {@link AreaCodeMap} initialized with {@code sortedAreaCodeMap}.
+   * Gets the size of the provided area code map storage. The map storage passed-in will be filled
+   * as a result.
+   */
+  private static int getSizeOfAreaCodeMapStorage(AreaCodeMapStorageStrategy mapStorage,
+      SortedMap<Integer, String> areaCodeMap) throws IOException {
+    mapStorage.readFromSortedMap(areaCodeMap);
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+    mapStorage.writeExternal(objectOutputStream);
+    objectOutputStream.flush();
+    int sizeOfStorage = byteArrayOutputStream.size();
+    objectOutputStream.close();
+    return sizeOfStorage;
+  }
+
+  private AreaCodeMapStorageStrategy createDefaultMapStorage() {
+    return new DefaultMapStorage(countryCallingCode, isLeadingZeroPossible);
+  }
+
+  private AreaCodeMapStorageStrategy createFlyweightMapStorage() {
+    return new FlyweightMapStorage(countryCallingCode, isLeadingZeroPossible);
+  }
+
+  /**
+   * Gets the smaller area code map storage strategy according to the provided area code map. It
+   * actually uses (outputs the data to a stream) both strategies and retains the best one which
+   * make this method quite expensive.
+   */
+  // @VisibleForTesting
+  AreaCodeMapStorageStrategy getSmallerMapStorage(SortedMap<Integer, String> areaCodeMap) {
+    try {
+      AreaCodeMapStorageStrategy flyweightMapStorage = createFlyweightMapStorage();
+      int sizeOfFlyweightMapStorage = getSizeOfAreaCodeMapStorage(flyweightMapStorage, areaCodeMap);
+
+      AreaCodeMapStorageStrategy defaultMapStorage = createDefaultMapStorage();
+      int sizeOfDefaultMapStorage = getSizeOfAreaCodeMapStorage(defaultMapStorage, areaCodeMap);
+
+      return sizeOfFlyweightMapStorage < sizeOfDefaultMapStorage
+          ? flyweightMapStorage : defaultMapStorage;
+    } catch (IOException e) {
+      LOGGER.severe(e.getMessage());
+      return createFlyweightMapStorage();
+    }
+  }
+
+  /**
+   * Creates an {@link AreaCodeMap} initialized with {@code sortedAreaCodeMap}.  Note that the
+   * underlying implementation of this method is expensive thus should not be called by
+   * time-critical applications.
    *
    * @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);
+    areaCodeMapStorage = getSmallerMapStorage(sortedAreaCodeMap);
   }
 
   /**
    * Supports Java Serialization.
    */
   public void readExternal(ObjectInput objectInput) throws IOException {
-    numOfEntries = objectInput.readInt();
-    if (phoneNumberPrefixes == null || phoneNumberPrefixes.length < numOfEntries) {
-      phoneNumberPrefixes = new int[numOfEntries];
+    // Read the area code map storage strategy flag.
+    boolean useFlyweightMapStorage = objectInput.readBoolean();
+    if (useFlyweightMapStorage) {
+      areaCodeMapStorage = new FlyweightMapStorage(countryCallingCode, isLeadingZeroPossible);
+    } else {
+      areaCodeMapStorage = new DefaultMapStorage(countryCallingCode, isLeadingZeroPossible);
     }
-    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());
-    }
+    areaCodeMapStorage.readExternal(objectInput);
   }
 
   /**
    * 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);
-    }
+    objectOutput.writeBoolean(areaCodeMapStorage.isFlyweight());
+    areaCodeMapStorage.writeExternal(objectOutput);
   }
 
   /**
@@ -110,13 +148,15 @@
    * @return  the description of the geographical area
    */
   String lookup(PhoneNumber number) {
+    int numOfEntries = areaCodeMapStorage.getNumOfEntries();
     if (numOfEntries == 0) {
       return "";
     }
-    long phonePrefix =
-        Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
+    long phonePrefix = isLeadingZeroPossible
+        ? Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number))
+        : Long.parseLong(phoneUtil.getNationalSignificantNumber(number));
     int currentIndex = numOfEntries - 1;
-    SortedSet<Integer> currentSetOfLengths = possibleLengths;
+    SortedSet<Integer> currentSetOfLengths = areaCodeMapStorage.getPossibleLengths();
     while (currentSetOfLengths.size() > 0) {
       Integer possibleLength = currentSetOfLengths.last();
       String phonePrefixStr = String.valueOf(phonePrefix);
@@ -127,17 +167,18 @@
       if (currentIndex < 0) {
         return "";
       }
-      if (phonePrefix == phoneNumberPrefixes[currentIndex]) {
-        return descriptions[currentIndex];
+      int currentPrefix = areaCodeMapStorage.getPrefix(currentIndex);
+      if (phonePrefix == currentPrefix) {
+        return areaCodeMapStorage.getDescription(currentIndex);
       }
-      currentSetOfLengths = possibleLengths.headSet(possibleLength);
+      currentSetOfLengths = currentSetOfLengths.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
+   * Does a binary search for {@code value} in the provided 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.
    */
@@ -145,9 +186,10 @@
     int current = 0;
     while (start <= end) {
       current = (start + end) / 2;
-      if (phoneNumberPrefixes[current] == value) {
+      int currentValue = areaCodeMapStorage.getPrefix(current);
+      if (currentValue == value) {
         return current;
-      } else if (phoneNumberPrefixes[current] > value) {
+      } else if (currentValue > value) {
         current--;
         end = current;
       } else {
@@ -163,10 +205,15 @@
   @Override
   public String toString() {
     StringBuilder output = new StringBuilder();
+    int numOfEntries = areaCodeMapStorage.getNumOfEntries();
+
     for (int i = 0; i < numOfEntries; i++) {
-      output.append(phoneNumberPrefixes[i]);
+      if (!isLeadingZeroPossible) {
+        output.append(countryCallingCode);
+      }
+      output.append(areaCodeMapStorage.getPrefix(i));
       output.append("|");
-      output.append(descriptions[i]);
+      output.append(areaCodeMapStorage.getDescription(i));
       output.append("\n");
     }
     return output.toString();
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java b/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java
new file mode 100644
index 0000000..65fb2bd
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/AreaCodeMapStorageStrategy.java
@@ -0,0 +1,164 @@
+/*
+ * 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.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.SortedMap;
+import java.util.TreeSet;
+
+/**
+ * Abstracts the way area code data is stored into memory and serialized to a stream.
+ *
+ * @author Philippe Liard
+ */
+// @VisibleForTesting
+abstract class AreaCodeMapStorageStrategy {
+  protected final int countryCallingCode;
+  protected final boolean isLeadingZeroPossible;
+  protected int numOfEntries = 0;
+  protected final TreeSet<Integer> possibleLengths = new TreeSet<Integer>();
+
+  /**
+   * Constructs a new area code map storage strategy from the provided country calling code and
+   * boolean parameter.
+   *
+   * @param countryCallingCode  the country calling code of the number prefixes contained in the map
+   * @param isLeadingZeroPossible  whether the phone number prefixes belong to a region which
+   *    {@link PhoneNumberUtil#isLeadingZeroPossible isLeadingZeroPossible}
+   */
+  public AreaCodeMapStorageStrategy(int countryCallingCode, boolean isLeadingZeroPossible) {
+    this.countryCallingCode = countryCallingCode;
+    this.isLeadingZeroPossible = isLeadingZeroPossible;
+  }
+
+  /**
+   * Returns whether the underlying implementation of this abstract class is flyweight.
+   * It is expected to be flyweight if it implements the {@code FlyweightMapStorage} class.
+   *
+   * @return  whether the underlying implementation of this abstract class is flyweight
+   */
+  public abstract boolean isFlyweight();
+
+  /**
+   * @return  the number of entries contained in the area code map
+   */
+  public int getNumOfEntries() {
+    return numOfEntries;
+  }
+
+  /**
+   * @return  the set containing the possible lengths of prefixes
+   */
+  public TreeSet<Integer> getPossibleLengths() {
+    return possibleLengths;
+  }
+
+  /**
+   * Gets the phone number prefix located at the provided {@code index}.
+   *
+   * @param index  the index of the prefix that needs to be returned
+   * @return  the phone number prefix at the provided index
+   */
+  public abstract int getPrefix(int index);
+
+  /**
+   * Gets the description corresponding to the phone number prefix located at the provided {@code
+   * index}.
+   *
+   * @param index  the index of the phone number prefix that needs to be returned
+   * @return  the description corresponding to the phone number prefix at the provided index
+   */
+  public abstract String getDescription(int index);
+
+  /**
+   * Sets the internal state of the underlying storage implementation from the provided {@code
+   * sortedAreaCodeMap} that maps phone number prefixes to description strings.
+   *
+   * @param sortedAreaCodeMap  a sorted map that maps phone number prefixes including country
+   *    calling code to description strings
+   */
+  public abstract void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap);
+
+  /**
+   * Sets the internal state of the underlying storage implementation reading the provided {@code
+   * objectInput}.
+   *
+   * @param objectInput  the object input stream from which the area code map is read
+   * @throws IOException  if an error occurred reading the provided input stream
+   */
+  public abstract void readExternal(ObjectInput objectInput) throws IOException;
+
+  /**
+   * Writes the internal state of the underlying storage implementation to the provided {@code
+   * objectOutput}.
+   *
+   * @param objectOutput  the object output stream to which the area code map is written
+   * @throws IOException  if an error occurred writing to the provided output stream
+   */
+  public abstract void writeExternal(ObjectOutput objectOutput) throws IOException;
+
+  /**
+   * Utility class used to pass arguments by "reference".
+   */
+  protected static class Reference<T> {
+    private T data;
+
+    T get () {
+      return data;
+    }
+
+    void set (T data) {
+      this.data = data;
+    }
+  }
+
+  /**
+   * Removes the country calling code from the provided {@code prefix} if the country can't have any
+   * leading zero; otherwise it is left as it is. Sets the provided {@code lengthOfPrefixRef}
+   * parameter to the length of the resulting prefix.
+   *
+   * @param prefix  a phone number prefix containing a leading country calling code
+   * @param lengthOfPrefixRef  a "reference" to an integer set to the length of the resulting
+   *    prefix. This parameter is ignored when set to null.
+   * @return  the resulting prefix which may have been stripped
+   */
+  protected int stripPrefix(int prefix, Reference<Integer> lengthOfPrefixRef) {
+    int lengthOfCountryCode = (int) Math.log10(countryCallingCode) + 1;
+    int lengthOfPrefix = (int) Math.log10(prefix) + 1;
+    if (!isLeadingZeroPossible) {
+      lengthOfPrefix -= lengthOfCountryCode;
+      prefix -= countryCallingCode * (int) Math.pow(10, lengthOfPrefix);
+    }
+    if (lengthOfPrefixRef != null) {
+      lengthOfPrefixRef.set(lengthOfPrefix);
+    }
+    return prefix;
+  }
+
+  /**
+   * Removes the country calling code from the provided {@code prefix} if the country can't have any
+   * leading zero; otherwise it is left as it is.
+   *
+   * @param prefix  a phone number prefix containing a leading country calling code
+   * @return  the resulting prefix which may have been stripped
+   */
+  protected int stripPrefix(int prefix) {
+    return stripPrefix(prefix, null);
+  }
+}
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java b/java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java
new file mode 100644
index 0000000..03dfa15
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/DefaultMapStorage.java
@@ -0,0 +1,103 @@
+/*
+ * 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.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.SortedMap;
+
+/**
+ * Default area code map storage strategy that is used for data not containing description
+ * duplications. It is mainly intended to avoid the overhead of the string table management when it
+ * is actually unnecessary (i.e no string duplication).
+ *
+ * @author Shaopeng Jia
+ */
+class DefaultMapStorage extends AreaCodeMapStorageStrategy {
+
+  public DefaultMapStorage(int countryCallingCode, boolean isLeadingZeroPossible) {
+    super(countryCallingCode, isLeadingZeroPossible);
+  }
+
+  private int[] phoneNumberPrefixes;
+  private String[] descriptions;
+
+  @Override
+  public boolean isFlyweight() {
+    return false;
+  }
+
+  @Override
+  public int getPrefix(int index) {
+    return phoneNumberPrefixes[index];
+  }
+
+  @Override
+  public String getDescription(int index) {
+    return descriptions[index];
+  }
+
+  @Override
+  public void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap) {
+    numOfEntries = sortedAreaCodeMap.size();
+    phoneNumberPrefixes = new int[numOfEntries];
+    descriptions = new String[numOfEntries];
+    int index = 0;
+    for (int prefix : sortedAreaCodeMap.keySet()) {
+      Reference<Integer> lengthOfPrefixRef = new Reference<Integer>();
+      int strippedPrefix = stripPrefix(prefix, lengthOfPrefixRef);
+      phoneNumberPrefixes[index++] = strippedPrefix;
+      possibleLengths.add(lengthOfPrefixRef.get());
+    }
+    sortedAreaCodeMap.values().toArray(descriptions);
+  }
+
+  @Override
+  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());
+    }
+  }
+
+  @Override
+  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);
+    }
+  }
+}
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java b/java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java
new file mode 100644
index 0000000..abdf7c5
--- /dev/null
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/FlyweightMapStorage.java
@@ -0,0 +1,261 @@
+/*
+ * 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.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Flyweight area code map storage strategy that uses a table to store unique strings and shorts to
+ * store the prefix and description indexes when possible. It is particularly space-efficient when
+ * the provided area code map contains a lot of description duplicates.
+ *
+ * @author Philippe Liard
+ */
+class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
+  // Size of short and integer types in bytes.
+  private static final int SHORT_SIZE = Short.SIZE / 8;
+  private static final int INT_SIZE = Integer.SIZE / 8;
+
+  // The number of bytes used to store a phone number prefix.
+  private int prefixSizeInBytes;
+  // The number of bytes used to store a description index. It is computed from the size of the
+  // description pool containing all the strings.
+  private int descIndexSizeInBytes;
+
+  // Byte buffer of stripped phone number prefixes. A stripped phone number prefix is a phone number
+  // prefix omitting the country code.
+  private ByteBuffer phoneNumberPrefixes;
+  private ByteBuffer descriptionIndexes;
+
+  // Sorted string array of unique description strings.
+  private String[] descriptionPool;
+
+  public FlyweightMapStorage(int countryCallingCode, boolean isLeadingZeroPossible) {
+    super(countryCallingCode, isLeadingZeroPossible);
+  }
+
+  @Override
+  public boolean isFlyweight() {
+    return true;
+  }
+
+  /**
+   * Gets the minimum number of bytes that can be used to store the provided {@code value}.
+   */
+  private static int getOptimalNumberOfBytesForValue(int value) {
+    return value <= Short.MAX_VALUE ? SHORT_SIZE : INT_SIZE;
+  }
+
+  /**
+   * Stores the provided {@code value} to the provided byte {@code buffer} at the specified {@code
+   * index} using the provided {@code wordSize} in bytes. Note that only integer and short sizes are
+   * supported.
+   *
+   * @param buffer  the byte buffer to which the value is stored
+   * @param wordSize  the number of bytes used to store the provided value
+   * @param index  the index in bytes to which the value is stored
+   * @param value  the value that is stored assuming it does not require more than the specified
+   *    number of bytes.
+   */
+  private static void storeWordInBuffer(ByteBuffer buffer, int wordSize, int index, int value) {
+    index *= wordSize;
+
+    if (wordSize == SHORT_SIZE) {
+      buffer.putShort(index, (short) value);
+    } else {
+      buffer.putInt(index, value);
+    }
+  }
+
+  /**
+   * Reads the {@code value} at the specified {@code index} from the provided byte {@code buffer}.
+   * Note that only integer and short sizes are supported.
+   *
+   * @param buffer  the byte buffer from which the value is read
+   * @param wordSize  the number of bytes used to store the value
+   * @param index  the index in bytes where the value is read from
+   *
+   * @return  the value read from the buffer
+   */
+  private static int readWordFromBuffer(ByteBuffer buffer, int wordSize, int index) {
+    index *= wordSize;
+    return wordSize == SHORT_SIZE ? buffer.getShort(index) : buffer.getInt(index);
+  }
+
+  @Override
+  public int getPrefix(int index) {
+    return readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, index);
+  }
+
+  @Override
+  public String getDescription(int index) {
+    return descriptionPool[readWordFromBuffer(descriptionIndexes, descIndexSizeInBytes, index)];
+  }
+
+  @Override
+  public void readFromSortedMap(SortedMap<Integer, String> sortedAreaCodeMap) {
+    SortedSet<String> descriptionsSet = new TreeSet<String>();
+    numOfEntries = sortedAreaCodeMap.size();
+    prefixSizeInBytes = getOptimalNumberOfBytesForValue(stripPrefix(sortedAreaCodeMap.lastKey()));
+    phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
+    Map<Integer, Integer> strippedToUnstrippedPrefixes = new HashMap<Integer, Integer>();
+
+    // Fill the phone number prefixes byte buffer, the set of possible lengths of prefixes and the
+    // description set.
+    int index = 0;
+    for (Entry<Integer, String> entry : sortedAreaCodeMap.entrySet()) {
+      int prefix = entry.getKey();
+      Reference<Integer> lengthOfPrefixRef = new Reference<Integer>();
+      int strippedPrefix = stripPrefix(prefix, lengthOfPrefixRef);
+      strippedToUnstrippedPrefixes.put(strippedPrefix, prefix);
+      storeWordInBuffer(phoneNumberPrefixes, prefixSizeInBytes, index++, strippedPrefix);
+      possibleLengths.add(lengthOfPrefixRef.get());
+      descriptionsSet.add(entry.getValue());
+    }
+
+    // Create the description pool.
+    descIndexSizeInBytes = getOptimalNumberOfBytesForValue(descriptionsSet.size() - 1);
+    descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes);
+    descriptionPool = new String[descriptionsSet.size()];
+    descriptionsSet.toArray(descriptionPool);
+
+    // Map the phone number prefixes to the descriptions.
+    index = 0;
+    for (int i = 0; i < numOfEntries; i++) {
+      int strippedPrefix = readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, i);
+      int prefix = strippedToUnstrippedPrefixes.get(strippedPrefix);
+      String description = sortedAreaCodeMap.get(prefix);
+      int positionIndescriptionPool =
+          Arrays.binarySearch(descriptionPool, description, new Comparator<String>() {
+            public int compare(String o1, String o2) { return o1.compareTo(o2); }
+          });
+      storeWordInBuffer(descriptionIndexes, descIndexSizeInBytes, index++,
+                        positionIndescriptionPool);
+    }
+  }
+
+  /**
+   * Stores a value which is read from the provided {@code objectInput} to the provided byte {@code
+   * buffer} at the specified {@code index}.
+   *
+   * @param objectInput  the object input stream from which the value is read
+   * @param wordSize  the number of bytes used to store the value read from the stream
+   * @param outputBuffer  the byte buffer to which the value is stored
+   * @param index  the index in bytes where the value is stored in the buffer
+   * @throws IOException  if an error occurred reading from the object input stream
+   */
+  private static void readExternalWord(ObjectInput objectInput, int wordSize,
+                                       ByteBuffer outputBuffer, int index) throws IOException {
+    index *= wordSize;
+    if (wordSize == SHORT_SIZE) {
+      outputBuffer.putShort(index, objectInput.readShort());
+    } else {
+      outputBuffer.putInt(index, objectInput.readInt());
+    }
+  }
+
+  @Override
+  public void readExternal(ObjectInput objectInput) throws IOException {
+    // Read binary words sizes.
+    prefixSizeInBytes = objectInput.readInt();
+    descIndexSizeInBytes = objectInput.readInt();
+    // Read possible lengths.
+    int sizeOfLengths = objectInput.readInt();
+    possibleLengths.clear();
+    for (int i = 0; i < sizeOfLengths; i++) {
+      possibleLengths.add(objectInput.readInt());
+    }
+    // Read description pool size.
+    int descriptionPoolSize = objectInput.readInt();
+    // Read description pool.
+    if (descriptionPool == null || descriptionPool.length < descriptionPoolSize) {
+      descriptionPool = new String[descriptionPoolSize];
+    }
+    for (int i = 0; i < descriptionPoolSize; i++) {
+      String description = objectInput.readUTF();
+      descriptionPool[i] = description;
+    }
+    // Read entries.
+    numOfEntries = objectInput.readInt();
+    if (phoneNumberPrefixes == null || phoneNumberPrefixes.capacity() < numOfEntries) {
+        phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
+    }
+    if (descriptionIndexes == null || descriptionIndexes.capacity() < numOfEntries) {
+      descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes);
+    }
+    for (int i = 0; i < numOfEntries; i++) {
+      readExternalWord(objectInput, prefixSizeInBytes, phoneNumberPrefixes, i);
+      readExternalWord(objectInput, descIndexSizeInBytes, descriptionIndexes, i);
+    }
+  }
+
+  /**
+   * Writes the value read from the provided byte {@code buffer} at the specified {@code index} to
+   * the provided {@code objectOutput}.
+   *
+   * @param objectOutput  the object output stream to which the value is written
+   * @param wordSize  the number of bytes used to store the value
+   * @param inputBuffer  the byte buffer from which the value is read
+   * @param index  the index of the value in the the byte buffer
+   * @throws IOException if an error occurred writing to the provided object output stream
+   */
+  private static void writeExternalWord(ObjectOutput objectOutput, int wordSize,
+                                        ByteBuffer inputBuffer, int index) throws IOException {
+    index *= wordSize;
+    if (wordSize == SHORT_SIZE) {
+      objectOutput.writeShort(inputBuffer.getShort(index));
+    } else {
+      objectOutput.writeInt(inputBuffer.getInt(index));
+    }
+  }
+
+  @Override
+  public void writeExternal(ObjectOutput objectOutput) throws IOException {
+    // Write binary words sizes.
+    objectOutput.writeInt(prefixSizeInBytes);
+    objectOutput.writeInt(descIndexSizeInBytes);
+    // Write possible lengths.
+    int sizeOfLengths = possibleLengths.size();
+    objectOutput.writeInt(sizeOfLengths);
+    for (Integer length : possibleLengths) {
+      objectOutput.writeInt(length);
+    }
+    // Write description pool size.
+    objectOutput.writeInt(descriptionPool.length);
+    // Write description pool.
+    for (String description : descriptionPool) {
+      objectOutput.writeUTF(description);
+    }
+    // Write entries.
+    objectOutput.writeInt(numOfEntries);
+    for (int i = 0; i < numOfEntries; i++) {
+      writeExternalWord(objectOutput, prefixSizeInBytes, phoneNumberPrefixes, i);
+      writeExternalWord(objectOutput, descIndexSizeInBytes, descriptionIndexes, i);
+    }
+  }
+}
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java b/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
index 1489b6b..ce1ab47 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
@@ -79,18 +79,18 @@
       return null;
     }
     if (!availablePhonePrefixMaps.containsKey(fileName)) {
-      loadAreaCodeMapFromFile(fileName);
+      loadAreaCodeMapFromFile(fileName, countryCallingCode);
     }
     return availablePhonePrefixMaps.get(fileName);
   }
 
-  private void loadAreaCodeMapFromFile(String fileName) {
+  private void loadAreaCodeMapFromFile(String fileName, int countryCallingCode) {
     InputStream source =
         PhoneNumberOfflineGeocoder.class.getResourceAsStream(phonePrefixDataDirectory + fileName);
     ObjectInputStream in;
     try {
       in = new ObjectInputStream(source);
-      AreaCodeMap map = new AreaCodeMap();
+      AreaCodeMap map = new AreaCodeMap(countryCallingCode);
       map.readExternal(in);
       availablePhonePrefixMaps.put(fileName, map);
     } catch (IOException e) {
@@ -147,6 +147,9 @@
    * @return  a text description for the given language code for the given phone number
    */
   public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
+    if (!phoneUtil.isValidNumber(number)) {
+      return "";
+    }
     String areaDescription =
         getAreaDescriptionForNumber(
             number, languageCode.getLanguage(), "",  // No script is specified.
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/1_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/1_en
index d69ae59..5e3e83f 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/1_en
+++ 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
index f967ac9..4fa31ff 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/31_nl
+++ 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/33_fr b/java/src/com/google/i18n/phonenumbers/geocoding/data/33_fr
index 22a814f..29011e9 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/33_fr
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/33_fr
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
index a6efe20..f9f7e4c 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/34_es
+++ 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/351_pt b/java/src/com/google/i18n/phonenumbers/geocoding/data/351_pt
index 7602825..9095abd 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/351_pt
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/351_pt
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/39_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/39_en
index 70770e8..fada2f3 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/39_en
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/39_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/39_it b/java/src/com/google/i18n/phonenumbers/geocoding/data/39_it
index fe09c12..3f5de0f 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/39_it
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/39_it
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_de b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_de
index 9abebd2..e0021ed 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_de
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_de
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_en
index 667abf2..f5c26cc 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_en
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_fr b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_fr
index 3d8b7e1..d417de2 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_fr
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_fr
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_it b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_it
index 16fe35d..845a7f6 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/41_it
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/41_it
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
index 270c2c9..9be50ca 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/43_de
+++ 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
index d938bb2..f826dc7 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/44_en
+++ 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
index c566c43..a8ebc9c 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/46_sv
+++ 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
index 87926bd..0df583f 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/49_de
+++ 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
index d4df906..5c5f5ec 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/54_es
+++ 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
index 3cab21f..7c2edf9 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/55_pt
+++ 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
index f36465f..deb60d9 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/56_es
+++ 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/7_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/7_en
index f0521db..b39cd8a 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/7_en
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/7_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/81_ja b/java/src/com/google/i18n/phonenumbers/geocoding/data/81_ja
index d117824..7efb738 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/81_ja
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/81_ja
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
index 964e026..fdeaf4b 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_en
+++ 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
index 69c8afe..5e96f81 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_ko
+++ 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
index eccb8a7..f7f64a9 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh
+++ 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
index e677541..a2e1825 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/82_zh_Hant
+++ 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
index 82239cd..ec58dc5 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/86_zh
+++ 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/886_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/886_en
index ede9054..b17f140 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/886_en
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/886_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh b/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh
index cf47b0a..7b81483 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh_Hant b/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh_Hant
index 1435c4e..130d643 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh_Hant
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/886_zh_Hant
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/90_en b/java/src/com/google/i18n/phonenumbers/geocoding/data/90_en
index fd8a282..3efa0e3 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/90_en
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/90_en
Binary files differ
diff --git a/java/src/com/google/i18n/phonenumbers/geocoding/data/90_tr b/java/src/com/google/i18n/phonenumbers/geocoding/data/90_tr
index 1b6f56d..741cbd5 100644
--- a/java/src/com/google/i18n/phonenumbers/geocoding/data/90_tr
+++ b/java/src/com/google/i18n/phonenumbers/geocoding/data/90_tr
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
index 07b9451..7d17975 100644
--- a/java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/AreaCodeMapTest.java
@@ -26,8 +26,6 @@
 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
@@ -35,85 +33,161 @@
  * @author Shaopeng Jia
  */
 public class AreaCodeMapTest extends TestCase {
-  private final AreaCodeMap areaCodeMap = new AreaCodeMap();
+  private final AreaCodeMap areaCodeMapForUS = new AreaCodeMap(1);
+  private final AreaCodeMap areaCodeMapForIT = new AreaCodeMap(39);
   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> sortedMapForUS = new TreeMap<Integer, String>();
+    sortedMapForUS.put(1212, "New York");
+    sortedMapForUS.put(1480, "Arizona");
+    sortedMapForUS.put(1650, "California");
+    sortedMapForUS.put(1907, "Alaska");
+    sortedMapForUS.put(1201664, "Westwood, NJ");
+    sortedMapForUS.put(1480893, "Phoenix, AZ");
+    sortedMapForUS.put(1501372, "Little Rock, AR");
+    sortedMapForUS.put(1626308, "Alhambra, CA");
+    sortedMapForUS.put(1650345, "San Mateo, CA");
+    sortedMapForUS.put(1867993, "Dawson, YT");
+    sortedMapForUS.put(1972480, "Richardson, TX");
+
+    areaCodeMapForUS.readAreaCodeMap(sortedMapForUS);
+
+    SortedMap<Integer, String> sortedMapForIT = new TreeMap<Integer, String>();
+    sortedMapForIT.put(3902, "Milan");
+    sortedMapForIT.put(3906, "Rome");
+    sortedMapForIT.put(39010, "Genoa");
+    sortedMapForIT.put(390131, "Alessandria");
+    sortedMapForIT.put(390321, "Novara");
+    sortedMapForIT.put(390975, "Potenza");
+
+    areaCodeMapForIT.readAreaCodeMap(sortedMapForIT);
+  }
+
+  private static SortedMap<Integer, String> createDefaultStorageMapCandidate() {
+    SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
+    // Make the area codes bigger to store them using integer.
+    sortedMap.put(121212345, "New York");
+    sortedMap.put(148034434, "Arizona");
+    return sortedMap;
+  }
+
+  private static SortedMap<Integer, String> createFlyweightStorageMapCandidate() {
     SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
     sortedMap.put(1212, "New York");
+    sortedMap.put(1213, "New York");
+    sortedMap.put(1214, "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");
+    return sortedMap;
+  }
 
-    areaCodeMap.readAreaCodeMap(sortedMap);
+  public void testGetSmallerMapStorageChoosesDefaultImpl() {
+    AreaCodeMapStorageStrategy mapStorage =
+        new AreaCodeMap(1).getSmallerMapStorage(createDefaultStorageMapCandidate());
+    assertFalse(mapStorage.isFlyweight());
+  }
+
+  public void testGetSmallerMapStorageChoosesFlyweightImpl() {
+    AreaCodeMapStorageStrategy mapStorage =
+        new AreaCodeMap(1).getSmallerMapStorage(createFlyweightStorageMapCandidate());
+    assertTrue(mapStorage.isFlyweight());
   }
 
   public void testLookupInvalidNumber_US() {
     // central office code cannot start with 1.
     number.setCountryCode(1).setNationalNumber(2121234567L);
-    assertEquals("New York", areaCodeMap.lookup(number));
+    assertEquals("New York", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumber_NJ() {
     number.setCountryCode(1).setNationalNumber(2016641234L);
-    assertEquals("Westwood, NJ", areaCodeMap.lookup(number));
+    assertEquals("Westwood, NJ", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumber_NY() {
     number.setCountryCode(1).setNationalNumber(2126641234L);
-    assertEquals("New York", areaCodeMap.lookup(number));
+    assertEquals("New York", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumber_CA_1() {
     number.setCountryCode(1).setNationalNumber(6503451234L);
-    assertEquals("San Mateo, CA", areaCodeMap.lookup(number));
+    assertEquals("San Mateo, CA", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumber_CA_2() {
     number.setCountryCode(1).setNationalNumber(6502531234L);
-    assertEquals("California", areaCodeMap.lookup(number));
+    assertEquals("California", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumberFound_TX() {
     number.setCountryCode(1).setNationalNumber(9724801234L);
-    assertEquals("Richardson, TX", areaCodeMap.lookup(number));
+    assertEquals("Richardson, TX", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumberNotFound_TX() {
     number.setCountryCode(1).setNationalNumber(9724811234L);
-    assertEquals("", areaCodeMap.lookup(number));
+    assertEquals("", areaCodeMapForUS.lookup(number));
   }
 
   public void testLookupNumber_CH() {
     number.setCountryCode(41).setNationalNumber(446681300L);
-    assertEquals("", areaCodeMap.lookup(number));
+    assertEquals("", areaCodeMapForUS.lookup(number));
   }
 
-  public void testReadWriteExternal() {
-    try {
-      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-      ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
-      areaCodeMap.writeExternal(objectOutputStream);
-      objectOutputStream.flush();
+  public void testLookupNumber_IT() {
+    number.setCountryCode(39).setNationalNumber(212345678L).setItalianLeadingZero(true);
+    assertEquals("Milan", areaCodeMapForIT.lookup(number));
 
-      AreaCodeMap newAreaCodeMap = new AreaCodeMap();
-      newAreaCodeMap.readExternal(
-          new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
+    number.setNationalNumber(612345678L);
+    assertEquals("Rome", areaCodeMapForIT.lookup(number));
 
-      assertEquals(areaCodeMap.toString(), newAreaCodeMap.toString());
-    } catch (IOException e) {
-      LOGGER.log(Level.SEVERE, e.getMessage());
-      fail();
-    }
+    number.setNationalNumber(3211234L);
+    assertEquals("Novara", areaCodeMapForIT.lookup(number));
+
+    // A mobile number
+    number.setNationalNumber(321123456L).setItalianLeadingZero(false);
+    assertEquals("", areaCodeMapForIT.lookup(number));
+
+    // An invalid number (too short)
+    number.setNationalNumber(321123L).setItalianLeadingZero(true);
+    assertEquals("Novara", areaCodeMapForIT.lookup(number));
+  }
+
+  /**
+   * Creates a new area code map serializing the provided area code map to a stream and then reading
+   * this stream. The resulting area code map is expected to be strictly equal to the provided one
+   * from which it was generated.
+   */
+  private static AreaCodeMap createNewAreaCodeMap(AreaCodeMap areaCodeMap)
+      throws IOException {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+    areaCodeMap.writeExternal(objectOutputStream);
+    objectOutputStream.flush();
+
+    AreaCodeMap newAreaCodeMap = new AreaCodeMap(1);
+    newAreaCodeMap.readExternal(
+        new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
+    return newAreaCodeMap;
+  }
+
+  public void testReadWriteExternalWithDefaultStrategy() throws IOException {
+    AreaCodeMap localAreaCodeMap = new AreaCodeMap(1);
+    localAreaCodeMap.readAreaCodeMap(createDefaultStorageMapCandidate());
+    assertFalse(localAreaCodeMap.getAreaCodeMapStorage().isFlyweight());
+
+    AreaCodeMap newAreaCodeMap;
+    newAreaCodeMap = createNewAreaCodeMap(localAreaCodeMap);
+    assertEquals(localAreaCodeMap.toString(), newAreaCodeMap.toString());
+  }
+
+  public void testReadWriteExternalWithFlyweightStrategy() throws IOException {
+    AreaCodeMap localAreaCodeMap = new AreaCodeMap(1);
+    localAreaCodeMap.readAreaCodeMap(createFlyweightStorageMapCandidate());
+    assertTrue(localAreaCodeMap.getAreaCodeMapStorage().isFlyweight());
+
+    AreaCodeMap newAreaCodeMap;
+    newAreaCodeMap = createNewAreaCodeMap(localAreaCodeMap);
+    assertEquals(localAreaCodeMap.toString(), newAreaCodeMap.toString());
   }
 }
diff --git a/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java b/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
index 0fbdafb..ec5f9bc 100644
--- a/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
+++ b/java/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
@@ -39,12 +39,16 @@
       new PhoneNumber().setCountryCode(82).setNationalNumber(322123456L);
   private static final PhoneNumber KO_NUMBER3 =
       new PhoneNumber().setCountryCode(82).setNationalNumber(6421234567L);
+  private static final PhoneNumber KO_INVALID_NUMBER =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(1234L);
   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 US_INVALID_NUMBER =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(1234567890L);
   private static final PhoneNumber BS_NUMBER1 =
       new PhoneNumber().setCountryCode(1).setNationalNumber(2423651234L);
   private static final PhoneNumber AU_NUMBER =
@@ -90,4 +94,9 @@
     assertEquals("\uC81C\uC8FC",
         geocoder.getDescriptionForNumber(KO_NUMBER3, Locale.KOREAN));
   }
+
+  public void testGetDescritionForInvaildNumber() {
+    assertEquals("", geocoder.getDescriptionForNumber(KO_INVALID_NUMBER, Locale.ENGLISH));
+    assertEquals("", geocoder.getDescriptionForNumber(US_INVALID_NUMBER, Locale.ENGLISH));
+  }
 }
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
index 6ef7841..74ef19c 100644
--- a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/1_en
+++ 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
index 964e026..fdeaf4b 100644
--- a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_en
+++ 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
index 69c8afe..5e96f81 100644
--- a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/82_ko
+++ 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
deleted file mode 100644
index 710e1e7..0000000
--- a/java/test/com/google/i18n/phonenumbers/geocoding/testing_data/86_en
+++ /dev/null
Binary files differ