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