blob: 1d0af1bb872acb30c78ff7bb835c2d007c9e6293 [file] [log] [blame]
/*
* 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.android.i18n.phonenumbers.geocoding;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.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/android/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>();
// @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;
}
/**
* 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. This method assumes the validity of the
* number passed in has already been checked.
*
* @param number a valid 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 getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
String langStr = languageCode.getLanguage();
String scriptStr = ""; // No script is specified
String regionStr = languageCode.getCountry();
String areaDescription =
getAreaDescriptionForNumber(number, langStr, scriptStr, regionStr);
return (areaDescription.length() > 0)
? areaDescription : getCountryNameForNumber(number, languageCode);
}
/**
* 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. This method explictly checkes the
* validity of the number passed in.
*
* @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, or empty
* string if the number passed in is invalid
*/
public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
if (!phoneUtil.isValidNumber(number)) {
return "";
}
return getDescriptionForValidNumber(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) {
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);
return (phonePrefixDescriptions != null) ? phonePrefixDescriptions.lookup(number) : "";
}
}