diff --git a/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java b/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java index 9281a13ce..b7ef967e8 100644 --- a/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java +++ b/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder.java @@ -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 availablePhonePrefixMaps = new HashMap(); // @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 index 000000000..523b3de03 --- /dev/null +++ b/java/geocoder/src/com/google/i18n/phonenumbers/geocoding/PrefixFileReader.java @@ -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 availablePhonePrefixMaps = new HashMap(); + + 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"); + } +} diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java index dfa237a2d..5e7ed0c3f 100644 --- a/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java +++ b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoderTest.java @@ -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 index 000000000..0d7b299c8 --- /dev/null +++ b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/PrefixFileReaderTest.java @@ -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 index 000000000..2c4b1b27b Binary files /dev/null and b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/54_en differ diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/config b/java/geocoder/test/com/google/i18n/phonenumbers/geocoding/testing_data/config index aab8c13e9..98d176c6b 100644 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 diff --git a/java/release_notes.txt b/java/release_notes.txt index 6fcdb38fd..af1c946ba 100644 --- a/java/release_notes.txt +++ b/java/release_notes.txt @@ -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 index 000000000..577d7e240 --- /dev/null +++ b/resources/test/geocoding/en/54.txt @@ -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