JAVA: Update to geocoder
authorroes@google.com <roes@google.com@ee073f10-1060-11df-b6a4-87a95322a99c>
Fri, 20 Sep 2013 08:44:19 +0000 (08:44 +0000)
committerroes@google.com <roes@google.com@ee073f10-1060-11df-b6a4-87a95322a99c>
Fri, 20 Sep 2013 08:44:19 +0000 (08:44 +0000)
git-svn-id: http://libphonenumber.googlecode.com/svn/trunk@610 ee073f10-1060-11df-b6a4-87a95322a99c

java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java
java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PrefixFileReader.java [new file with mode: 0644]
java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java
java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PrefixFileReaderTest.java [new file with mode: 0644]
java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/54_en [new file with mode: 0644]
java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/config
java/release_notes.txt
resources/test/geocoding/en/54.txt [new file with mode: 0644]

index 9281a13..b7ef967 100644 (file)
@@ -19,15 +19,9 @@ package com.google.i18n.phonenumbers.geocoding;
 import com.google.i18n.phonenumbers.PhoneNumberUtil;
 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.geocoding.PrefixFileReader;
 
-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.
@@ -38,75 +32,13 @@ 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 PrefixFileReader prefixFileReader = null;
+  
   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>();
 
   // @VisibleForTesting
   PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
-    this.phonePrefixDataDirectory = phonePrefixDataDirectory;
-    loadMappingFileProvider();
-  }
-
-  private void loadMappingFileProvider() {
-    InputStream source =
-        PhoneNumberOfflineGeocoder.class.getResourceAsStream(phonePrefixDataDirectory + "config");
-    ObjectInputStream in = null;
-    try {
-      in = new ObjectInputStream(source);
-      mappingFileProvider.readExternal(in);
-    } catch (IOException e) {
-      LOGGER.log(Level.WARNING, e.toString());
-    } finally {
-      close(in);
-    }
-  }
-
-  private AreaCodeMap getPhonePrefixDescriptions(
-      int prefixMapKey, String language, String script, String region) {
-    String fileName = mappingFileProvider.getFileName(prefixMapKey, 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 = null;
-    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());
-    } finally {
-      close(in);
-    }
-  }
-
-  private static void close(InputStream in) {
-    if (in != null) {
-      try {
-        in.close();
-      } catch (IOException e) {
-        LOGGER.log(Level.WARNING, e.toString());
-      }
-    }
+    prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory);
   }
 
   /**
@@ -162,8 +94,25 @@ public class PhoneNumberOfflineGeocoder {
     String scriptStr = "";  // No script is specified
     String regionStr = languageCode.getCountry();
 
-    String areaDescription =
-        getAreaDescriptionForNumber(number, langStr, scriptStr, regionStr);
+    String areaDescription;
+    String mobileToken = PhoneNumberUtil.getCountryMobileToken(number.getCountryCode());
+    String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
+    if (!mobileToken.equals("") && nationalNumber.startsWith(mobileToken)) {
+      // In some countries, eg. Argentina, mobile numbers have a mobile token before the national
+      // destination code, this should be removed before geocoding.
+      nationalNumber = nationalNumber.substring(mobileToken.length());
+      PhoneNumber copiedNumber = new PhoneNumber();
+      copiedNumber.setCountryCode(number.getCountryCode());
+      copiedNumber.setNationalNumber(Long.parseLong(nationalNumber));
+      if (nationalNumber.startsWith("0")) {
+        copiedNumber.setItalianLeadingZero(true);
+      }
+      areaDescription = prefixFileReader.getDescriptionForNumber(copiedNumber, langStr, scriptStr,
+                                                                 regionStr);
+    } else {
+      areaDescription = prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr,
+                                                                 regionStr);
+    }
     return (areaDescription.length() > 0)
         ? areaDescription : getCountryNameForNumber(number, languageCode);
   }
@@ -256,46 +205,4 @@ public class PhoneNumberOfflineGeocoder {
             numberType == PhoneNumberType.MOBILE ||
             numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
   }
-
-  /**
-   * 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) {
-    int countryCallingCode = number.getCountryCode();
-    // As the NANPA data is split into multiple files covering 3-digit areas, use a phone number
-    // prefix of 4 digits for NANPA instead, e.g. 1650.
-    int phonePrefix = (countryCallingCode != 1) ?
-        countryCallingCode : (1000 + (int) (number.getNationalNumber() / 10000000));
-    AreaCodeMap phonePrefixDescriptions =
-        getPhonePrefixDescriptions(phonePrefix, lang, script, region);
-    String description = (phonePrefixDescriptions != null)
-        ? phonePrefixDescriptions.lookup(number)
-        : null;
-    // When a location is not available in the requested language, fall back to English.
-    if ((description == null || description.length() == 0) && mayFallBackToEnglish(lang)) {
-      AreaCodeMap defaultMap = getPhonePrefixDescriptions(phonePrefix, "en", "", "");
-      if (defaultMap == null) {
-        return "";
-      }
-      description = defaultMap.lookup(number);
-    }
-    return description != null ? description : "";
-  }
-
-  private boolean mayFallBackToEnglish(String lang) {
-    // Don't fall back to English if the requested language is among the following:
-    // - Chinese
-    // - Japanese
-    // - Korean
-    return !lang.equals("zh") && !lang.equals("ja") && !lang.equals("ko");
-  }
 }
diff --git a/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PrefixFileReader.java b/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PrefixFileReader.java
new file mode 100644 (file)
index 0000000..523b3de
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 The Libphonenumber Authors
+ *
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A helper class doing file handling and lookup of phone number prefix mappings.
+ *
+ * @author Shaopeng Jia
+ */
+public class PrefixFileReader {
+  private static final Logger LOGGER = Logger.getLogger(PrefixFileReader.class.getName());
+
+  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>();
+
+  public PrefixFileReader(String phonePrefixDataDirectory) {
+    this.phonePrefixDataDirectory = phonePrefixDataDirectory;
+    loadMappingFileProvider();
+  }
+
+  private void loadMappingFileProvider() {
+    InputStream source =
+        PrefixFileReader.class.getResourceAsStream(phonePrefixDataDirectory + "config");
+    ObjectInputStream in = null;
+    try {
+      in = new ObjectInputStream(source);
+      mappingFileProvider.readExternal(in);
+    } catch (IOException e) {
+      LOGGER.log(Level.WARNING, e.toString());
+    } finally {
+      close(in);
+    }
+  }
+
+  private AreaCodeMap getPhonePrefixDescriptions(
+      int prefixMapKey, String language, String script, String region) {
+    String fileName = mappingFileProvider.getFileName(prefixMapKey, 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 =
+        PrefixFileReader.class.getResourceAsStream(phonePrefixDataDirectory + fileName);
+    ObjectInputStream in = null;
+    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());
+    } finally {
+      close(in);
+    }
+  }
+
+  private static void close(InputStream in) {
+    if (in != null) {
+      try {
+        in.close();
+      } catch (IOException e) {
+        LOGGER.log(Level.WARNING, e.toString());
+      }
+    }
+  }
+
+  /**
+   * Returns a 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  a text description in the given language for the given phone number, or an empty
+   *     string if a description is not available
+   */
+  public String getDescriptionForNumber(
+      PhoneNumber number, String lang, String script, String region) {
+    int countryCallingCode = number.getCountryCode();
+    // As the NANPA data is split into multiple files covering 3-digit areas, use a phone number
+    // prefix of 4 digits for NANPA instead, e.g. 1650.
+    int phonePrefix = (countryCallingCode != 1) ?
+        countryCallingCode : (1000 + (int) (number.getNationalNumber() / 10000000));
+    AreaCodeMap phonePrefixDescriptions =
+        getPhonePrefixDescriptions(phonePrefix, lang, script, region);
+    String description = (phonePrefixDescriptions != null)
+        ? phonePrefixDescriptions.lookup(number)
+        : null;
+    // When a location is not available in the requested language, fall back to English.
+    if ((description == null || description.length() == 0) && mayFallBackToEnglish(lang)) {
+      AreaCodeMap defaultMap = getPhonePrefixDescriptions(phonePrefix, "en", "", "");
+      if (defaultMap == null) {
+        return "";
+      }
+      description = defaultMap.lookup(number);
+    }
+    return description != null ? description : "";
+  }
+
+  private boolean mayFallBackToEnglish(String lang) {
+    // Don't fall back to English if the requested language is among the following:
+    // - Chinese
+    // - Japanese
+    // - Korean
+    return !lang.equals("zh") && !lang.equals("ja") && !lang.equals("ko");
+  }
+}
index dfa237a..5e7ed0c 100644 (file)
@@ -55,6 +55,8 @@ public class PhoneNumberOfflineGeocoderTest extends TestCase {
       new PhoneNumber().setCountryCode(1).setNationalNumber(2423651234L);
   private static final PhoneNumber AU_NUMBER =
       new PhoneNumber().setCountryCode(61).setNationalNumber(236618300L);
+  private static final PhoneNumber AR_MOBILE_NUMBER =
+      new PhoneNumber().setCountryCode(54).setNationalNumber(92214000000L);
   private static final PhoneNumber NUMBER_WITH_INVALID_COUNTRY_CODE =
       new PhoneNumber().setCountryCode(999).setNationalNumber(2423651234L);
   private static final PhoneNumber INTERNATIONAL_TOLL_FREE =
@@ -104,6 +106,11 @@ public class PhoneNumberOfflineGeocoderTest extends TestCase {
         geocoder.getDescriptionForNumber(KO_NUMBER2, Locale.KOREAN));
   }
 
+  public void testGetDescriptionForArgentinianMobileNumber() {
+    assertEquals("La Plata",
+        geocoder.getDescriptionForNumber(AR_MOBILE_NUMBER, Locale.ENGLISH));
+  }
+
   public void testGetDescriptionForFallBack() {
     // No fallback, as the location name for the given phone number is available in the requested
     // language.
diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PrefixFileReaderTest.java b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PrefixFileReaderTest.java
new file mode 100644 (file)
index 0000000..0d7b299
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 The Libphonenumber Authors
+ *
+ * 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;
+
+/**
+ * Unit tests for PrefixFileReader.java
+ *
+ * @author Cecilia Roes
+ */
+public class PrefixFileReaderTest extends TestCase {
+  private final PrefixFileReader reader = new PrefixFileReader(TEST_MAPPING_DATA_DIRECTORY);
+  private static final String TEST_MAPPING_DATA_DIRECTORY =
+      "/com/google/i18n/phonenumbers/geocoding/testing_data/";
+
+  private static final PhoneNumber KO_NUMBER =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(22123456L);
+  private static final PhoneNumber US_NUMBER1 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6502530000L);
+  private static final PhoneNumber US_NUMBER2 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(2128120000L);
+  private static final PhoneNumber US_NUMBER3 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6174240000L);
+  private static final PhoneNumber SE_NUMBER =
+      new PhoneNumber().setCountryCode(46).setNationalNumber(81234567L);
+
+  public void testGetDescriptionForNumberWithMapping() {
+    assertEquals("Kalifornien",
+                 reader.getDescriptionForNumber(US_NUMBER1, "de", "", "CH"));
+    assertEquals("CA",
+                 reader.getDescriptionForNumber(US_NUMBER1, "en", "", "AU"));
+    assertEquals("\uC11C\uC6B8",
+                 reader.getDescriptionForNumber(KO_NUMBER, "ko", "", ""));
+    assertEquals("Seoul",
+                 reader.getDescriptionForNumber(KO_NUMBER, "en", "", ""));
+  }
+
+  public void testGetDescriptionForNumberWithMissingMapping() {
+    assertEquals("", reader.getDescriptionForNumber(US_NUMBER3, "en", "", ""));
+  }
+
+  public void testGetDescriptionUsingFallbackLanguage() {
+    // Mapping file exists but the number isn't present, causing it to fallback.
+    assertEquals("New York, NY",
+                 reader.getDescriptionForNumber(US_NUMBER2, "de", "", "CH"));
+    // No mapping file exists, causing it to fallback.
+    assertEquals("New York, NY",
+                 reader.getDescriptionForNumber(US_NUMBER2, "sv", "", ""));
+  }
+
+  public void testGetDescriptionForNonFallbackLanguage() {
+    assertEquals("", reader.getDescriptionForNumber(US_NUMBER2, "ko", "", ""));
+  }
+
+  public void testGetDescriptionForNumberWithoutMappingFile() {
+    assertEquals("", reader.getDescriptionForNumber(SE_NUMBER, "sv", "", ""));
+    assertEquals("", reader.getDescriptionForNumber(SE_NUMBER, "en", "", ""));
+  }
+}
diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/54_en b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/54_en
new file mode 100644 (file)
index 0000000..2c4b1b2
Binary files /dev/null and b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/54_en differ
index aab8c13..98d176c 100644 (file)
Binary files a/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/config and b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/config differ
index 6fcdb38..af1c946 100644 (file)
@@ -1,3 +1,9 @@
+Sep 20, 2013: libphonenumber-5.8.3
+* Code changes:
+  - PhoneNumberOfflineGeocoder: Moved utility functionality to PrefixFileReader.
+  - Bug fix: Argentinian (and other countries with mobile tokens) mobile numbers now geocode
+             correctly.
+
 Sep 19, 2013: libphonenumber-5.8.2
 * Code changes:
   - New method in the PhoneNumberUtil API - getCountryMobileToken.
diff --git a/resources/test/geocoding/en/54.txt b/resources/test/geocoding/en/54.txt
new file mode 100644 (file)
index 0000000..577d7e2
--- /dev/null
@@ -0,0 +1,15 @@
+# Copyright (C) 2013 The Libphonenumber Authors
+
+# 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.
+
+542214|La Plata