diff --git a/java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java b/java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java index ed3d6d88ef..7f30022d5a 100644 --- a/java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java +++ b/java/carrier/src/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapper.java @@ -16,107 +16,71 @@ package com.google.i18n.phonenumbers; -import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; -import com.google.i18n.phonenumbers.metadata.DefaultMetadataDependenciesProvider; -import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader; import java.util.Locale; /** * A phone prefix mapper which provides carrier information related to a phone number. * * @author Cecilia Roes + * @deprecated Use {@link com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper} instead, + * which is the same class, but in a different package. */ +@Deprecated public class PhoneNumberToCarrierMapper { - private static PhoneNumberToCarrierMapper instance = null; - private final PrefixFileReader prefixFileReader; - private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + private static PhoneNumberToCarrierMapper instance = null; + private final com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper delegate; - // @VisibleForTesting - PhoneNumberToCarrierMapper(String phonePrefixDataDirectory) { - prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory); + @Deprecated + public PhoneNumberToCarrierMapper( + com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper delegate) { + this.delegate = delegate; } /** - * Gets a {@link PhoneNumberToCarrierMapper} instance to carry out international carrier lookup. - * - *

The {@link PhoneNumberToCarrierMapper} is implemented as a singleton. Therefore, calling - * this method multiple times will only result in one instance being created. - * - * @return a {@link PhoneNumberToCarrierMapper} instance + * @deprecated Use + * {@link com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper#getInstance()} + * instead */ public static synchronized PhoneNumberToCarrierMapper getInstance() { if (instance == null) { - instance = new PhoneNumberToCarrierMapper(DefaultMetadataDependenciesProvider.getInstance() - .getCarrierDataDirectory()); + instance = new PhoneNumberToCarrierMapper( + com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper.getInstance()); } return instance; } /** - * Returns a carrier name for the given phone number, in the language provided. The carrier name - * is the one the number was originally allocated to, however if the country supports mobile - * number portability the number might not belong to the returned carrier anymore. If no mapping - * is found an empty string is returned. - * - *

This method assumes the validity of the number passed in has already been checked, and that - * the number is suitable for carrier lookup. We consider mobile and pager numbers possible - * candidates for carrier lookup. - * - * @param number a valid phone number for which we want to get a carrier name - * @param languageCode the language code in which the name should be written - * @return a carrier name for the given phone number + * @deprecated Use + * {@link + * com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper#getNameForValidNumber(PhoneNumber, + * Locale)} instead. */ + @Deprecated public String getNameForValidNumber(PhoneNumber number, Locale languageCode) { - String langStr = languageCode.getLanguage(); - String scriptStr = ""; // No script is specified - String regionStr = languageCode.getCountry(); - - return prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr, regionStr); + return delegate.getNameForValidNumber(number, languageCode); } /** - * Gets the name of the carrier for the given phone number, in the language provided. As per - * {@link #getNameForValidNumber(PhoneNumber, Locale)} but explicitly checks the validity of - * the number passed in. - * - * @param number the phone number for which we want to get a carrier name - * @param languageCode the language code in which the name should be written - * @return a carrier name for the given phone number, or empty string if the number passed in is - * invalid + * @deprecated Use + * {@link + * com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper#getNameForNumber(PhoneNumber, + * Locale)} instead. */ + @Deprecated public String getNameForNumber(PhoneNumber number, Locale languageCode) { - PhoneNumberType numberType = phoneUtil.getNumberType(number); - if (isMobile(numberType)) { - return getNameForValidNumber(number, languageCode); - } - return ""; + return delegate.getNameForNumber(number, languageCode); } /** - * Gets the name of the carrier for the given phone number only when it is 'safe' to display to - * users. A carrier name is considered safe if the number is valid and for a region that doesn't - * support - * mobile number portability. - * - * @param number the phone number for which we want to get a carrier name - * @param languageCode the language code in which the name should be written - * @return a carrier name that is safe to display to users, or the empty string + * @deprecated Use + * {@link + * com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper#getSafeDisplayName(PhoneNumber, + * Locale)} instead. */ + @Deprecated public String getSafeDisplayName(PhoneNumber number, Locale languageCode) { - if (phoneUtil.isMobileNumberPortableRegion(phoneUtil.getRegionCodeForNumber(number))) { - return ""; - } - return getNameForNumber(number, languageCode); - } - - /** - * Checks if the supplied number type supports carrier lookup. - */ - private boolean isMobile(PhoneNumberType numberType) { - return (numberType == PhoneNumberType.MOBILE - || numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE - || numberType == PhoneNumberType.PAGER); + return delegate.getSafeDisplayName(number, languageCode); } } diff --git a/java/carrier/src/com/google/i18n/phonenumbers/carrier/PhoneNumberToCarrierMapper.java b/java/carrier/src/com/google/i18n/phonenumbers/carrier/PhoneNumberToCarrierMapper.java new file mode 100644 index 0000000000..64df19a74e --- /dev/null +++ b/java/carrier/src/com/google/i18n/phonenumbers/carrier/PhoneNumberToCarrierMapper.java @@ -0,0 +1,123 @@ +/* + * 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.carrier; + +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.metadata.DefaultMetadataDependenciesProvider; +import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader; +import java.util.Locale; + +/** + * A phone prefix mapper which provides carrier information related to a phone number. + * + * @author Cecilia Roes + */ +public class PhoneNumberToCarrierMapper { + private static PhoneNumberToCarrierMapper instance = null; + private final PrefixFileReader prefixFileReader; + + private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + + // @VisibleForTesting + PhoneNumberToCarrierMapper(String phonePrefixDataDirectory) { + prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory); + } + + /** + * Gets a {@link PhoneNumberToCarrierMapper} instance to carry out international carrier lookup. + * + *

The {@link PhoneNumberToCarrierMapper} is implemented as a singleton. Therefore, calling + * this method multiple times will only result in one instance being created. + * + * @return a {@link PhoneNumberToCarrierMapper} instance + */ + public static synchronized PhoneNumberToCarrierMapper getInstance() { + if (instance == null) { + instance = new PhoneNumberToCarrierMapper(DefaultMetadataDependenciesProvider.getInstance() + .getCarrierDataDirectory()); + } + return instance; + } + + /** + * Returns a carrier name for the given phone number, in the language provided. The carrier name + * is the one the number was originally allocated to, however if the country supports mobile + * number portability the number might not belong to the returned carrier anymore. If no mapping + * is found an empty string is returned. + * + *

This method assumes the validity of the number passed in has already been checked, and that + * the number is suitable for carrier lookup. We consider mobile and pager numbers possible + * candidates for carrier lookup. + * + * @param number a valid phone number for which we want to get a carrier name + * @param languageCode the language code in which the name should be written + * @return a carrier name for the given phone number + */ + public String getNameForValidNumber(PhoneNumber number, Locale languageCode) { + String langStr = languageCode.getLanguage(); + String scriptStr = ""; // No script is specified + String regionStr = languageCode.getCountry(); + + return prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr, regionStr); + } + + /** + * Gets the name of the carrier for the given phone number, in the language provided. As per + * {@link #getNameForValidNumber(PhoneNumber, Locale)} but explicitly checks the validity of + * the number passed in. + * + * @param number the phone number for which we want to get a carrier name + * @param languageCode the language code in which the name should be written + * @return a carrier name for the given phone number, or empty string if the number passed in is + * invalid + */ + public String getNameForNumber(PhoneNumber number, Locale languageCode) { + PhoneNumberType numberType = phoneUtil.getNumberType(number); + if (isMobile(numberType)) { + return getNameForValidNumber(number, languageCode); + } + return ""; + } + + /** + * Gets the name of the carrier for the given phone number only when it is 'safe' to display to + * users. A carrier name is considered safe if the number is valid and for a region that doesn't + * support + * mobile number portability. + * + * @param number the phone number for which we want to get a carrier name + * @param languageCode the language code in which the name should be written + * @return a carrier name that is safe to display to users, or the empty string + */ + public String getSafeDisplayName(PhoneNumber number, Locale languageCode) { + if (phoneUtil.isMobileNumberPortableRegion(phoneUtil.getRegionCodeForNumber(number))) { + return ""; + } + return getNameForNumber(number, languageCode); + } + + /** + * Checks if the supplied number type supports carrier lookup. + */ + private boolean isMobile(PhoneNumberType numberType) { + return (numberType == PhoneNumberType.MOBILE + || numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE + || numberType == PhoneNumberType.PAGER); + } +} \ No newline at end of file diff --git a/java/carrier/test/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapperTest.java b/java/carrier/test/com/google/i18n/phonenumbers/carrier/PhoneNumberToCarrierMapperTest.java similarity index 82% rename from java/carrier/test/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapperTest.java rename to java/carrier/test/com/google/i18n/phonenumbers/carrier/PhoneNumberToCarrierMapperTest.java index 8ca0e2d658..f8f7c1344a 100644 --- a/java/carrier/test/com/google/i18n/phonenumbers/PhoneNumberToCarrierMapperTest.java +++ b/java/carrier/test/com/google/i18n/phonenumbers/carrier/PhoneNumberToCarrierMapperTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.i18n.phonenumbers; +package com.google.i18n.phonenumbers.carrier; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import junit.framework.TestCase; @@ -26,8 +26,11 @@ * @author Cecilia Roes */ public class PhoneNumberToCarrierMapperTest extends TestCase { - private final PhoneNumberToCarrierMapper carrierMapper = - new PhoneNumberToCarrierMapper(TEST_MAPPING_DATA_DIRECTORY); + + private final com.google.i18n.phonenumbers.PhoneNumberToCarrierMapper carrierMapper = + new com.google.i18n.phonenumbers.PhoneNumberToCarrierMapper( + new com.google.i18n.phonenumbers.carrier.PhoneNumberToCarrierMapper( + TEST_MAPPING_DATA_DIRECTORY)); private static final String TEST_MAPPING_DATA_DIRECTORY = "/com/google/i18n/phonenumbers/carrier/testing_data/"; @@ -63,20 +66,20 @@ public class PhoneNumberToCarrierMapperTest extends TestCase { public void testGetNameForMobilePortableRegion() { assertEquals("British carrier", - carrierMapper.getNameForNumber(UK_MOBILE1, Locale.ENGLISH)); + carrierMapper.getNameForNumber(UK_MOBILE1, Locale.ENGLISH)); assertEquals("Brittisk operat\u00F6r", - carrierMapper.getNameForNumber(UK_MOBILE1, new Locale("sv", "SE"))); + carrierMapper.getNameForNumber(UK_MOBILE1, new Locale("sv", "SE"))); assertEquals("British carrier", - carrierMapper.getNameForNumber(UK_MOBILE1, Locale.FRENCH)); + carrierMapper.getNameForNumber(UK_MOBILE1, Locale.FRENCH)); // Returns an empty string because the UK implements mobile number portability. assertEquals("", carrierMapper.getSafeDisplayName(UK_MOBILE1, Locale.ENGLISH)); } public void testGetNameForNonMobilePortableRegion() { assertEquals("Angolan carrier", - carrierMapper.getNameForNumber(AO_MOBILE1, Locale.ENGLISH)); + carrierMapper.getNameForNumber(AO_MOBILE1, Locale.ENGLISH)); assertEquals("Angolan carrier", - carrierMapper.getSafeDisplayName(AO_MOBILE1, Locale.ENGLISH)); + carrierMapper.getSafeDisplayName(AO_MOBILE1, Locale.ENGLISH)); } public void testGetNameForFixedLineNumber() { @@ -85,13 +88,13 @@ public void testGetNameForFixedLineNumber() { // If the carrier information is present in the files and the method that assumes a valid // number is used, a carrier is returned. assertEquals("Angolan fixed line carrier", - carrierMapper.getNameForValidNumber(AO_FIXED2, Locale.ENGLISH)); + carrierMapper.getNameForValidNumber(AO_FIXED2, Locale.ENGLISH)); assertEquals("", carrierMapper.getNameForValidNumber(UK_FIXED2, Locale.ENGLISH)); } public void testGetNameForFixedOrMobileNumber() { assertEquals("US carrier", carrierMapper.getNameForNumber(US_FIXED_OR_MOBILE, - Locale.ENGLISH)); + Locale.ENGLISH)); } public void testGetNameForPagerNumber() { @@ -100,13 +103,13 @@ public void testGetNameForPagerNumber() { public void testGetNameForNumberWithNoDataFile() { assertEquals("", carrierMapper.getNameForNumber(NUMBER_WITH_INVALID_COUNTRY_CODE, - Locale.ENGLISH)); + Locale.ENGLISH)); assertEquals("", carrierMapper.getNameForNumber(INTERNATIONAL_TOLL_FREE, - Locale.ENGLISH)); + Locale.ENGLISH)); assertEquals("", carrierMapper.getNameForValidNumber(NUMBER_WITH_INVALID_COUNTRY_CODE, - Locale.ENGLISH)); + Locale.ENGLISH)); assertEquals("", carrierMapper.getNameForValidNumber(INTERNATIONAL_TOLL_FREE, - Locale.ENGLISH)); + Locale.ENGLISH)); } public void testGetNameForNumberWithMissingPrefix() { diff --git a/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java b/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java index 217587ba95..5a0cb4df5f 100644 --- a/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java +++ b/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java @@ -16,164 +16,66 @@ package com.google.i18n.phonenumbers; -import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; -import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; /** * An offline mapper from phone numbers to time zones. + * + * @deprecated Use {@link com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper} + * instead, which is the same class, but in a different package. */ +@Deprecated public class PhoneNumberToTimeZonesMapper { - private static final String MAPPING_DATA_DIRECTORY = - "/com/google/i18n/phonenumbers/timezones/data/"; - private static final String MAPPING_DATA_FILE_NAME = "map_data"; - // This is defined by ICU as the unknown time zone. - private static final String UNKNOWN_TIMEZONE = "Etc/Unknown"; - // A list with the ICU unknown time zone as single element. - // @VisibleForTesting - static final List UNKNOWN_TIME_ZONE_LIST = new ArrayList(1); - static { - UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE); - } - - private static final Logger logger = - Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName()); - - private PrefixTimeZonesMap prefixTimeZonesMap = null; - - // @VisibleForTesting - PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) { - this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile( - prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME); - } - - private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) { - this.prefixTimeZonesMap = prefixTimeZonesMap; - } - - private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) { - InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path); - ObjectInputStream in = null; - PrefixTimeZonesMap map = new PrefixTimeZonesMap(); - try { - in = new ObjectInputStream(source); - map.readExternal(in); - } catch (IOException e) { - logger.log(Level.WARNING, e.toString()); - } finally { - close(in); - } - return map; - } - private static void close(InputStream in) { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - logger.log(Level.WARNING, e.toString()); - } - } - } + private final com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper delegate; - /** - * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the - * map data in a thread-safe way. - */ - private static class LazyHolder { - private static final PhoneNumberToTimeZonesMapper INSTANCE; - static { - PrefixTimeZonesMap map = - loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME); - INSTANCE = new PhoneNumberToTimeZonesMapper(map); - } + @Deprecated + public PhoneNumberToTimeZonesMapper( + com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper delegate) { + this.delegate = delegate; } /** - * Gets a {@link PhoneNumberToTimeZonesMapper} instance. - * - *

The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling - * this method multiple times will only result in one instance being created. - * - * @return a {@link PhoneNumberToTimeZonesMapper} instance + * @deprecated Use + * {@link com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper#getInstance()} + * instead. */ + @Deprecated public static synchronized PhoneNumberToTimeZonesMapper getInstance() { - return LazyHolder.INSTANCE; + return new PhoneNumberToTimeZonesMapper( + com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper.getInstance()); } /** - * Returns a list of time zones to which a phone number belongs. - * - *

This method assumes the validity of the number passed in has already been checked, and that - * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates - * for geo-localization. - * - * @param number a valid phone number for which we want to get the time zones to which it belongs - * @return a list of the corresponding time zones or a single element list with the default - * unknown time zone if no other time zone was found or if the number was invalid + * @deprecated Use + * {@link + * com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper#getTimeZonesForGeographicalNumber(PhoneNumber)} + * instead. */ + @Deprecated public List getTimeZonesForGeographicalNumber(PhoneNumber number) { - return getTimeZonesForGeocodableNumber(number); + return delegate.getTimeZonesForGeographicalNumber(number); } /** - * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks - * the validity of the number passed in. - * - * @param number the phone number for which we want to get the time zones to which it belongs - * @return a list of the corresponding time zones or a single element list with the default - * unknown time zone if no other time zone was found or if the number was invalid + * @deprecated Use + * {@link + * com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper#getTimeZonesForNumber(PhoneNumber)} + * instead. */ + @Deprecated public List getTimeZonesForNumber(PhoneNumber number) { - PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number); - if (numberType == PhoneNumberType.UNKNOWN) { - return UNKNOWN_TIME_ZONE_LIST; - } else if (!PhoneNumberUtil.getInstance().isNumberGeographical( - numberType, number.getCountryCode())) { - return getCountryLevelTimeZonesforNumber(number); - } - return getTimeZonesForGeographicalNumber(number); + return delegate.getTimeZonesForNumber(number); } /** - * Returns a String with the ICU unknown time zone. + * @deprecated Use + * {@link + * com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper#getUnknownTimeZone()} + * instead. */ public static String getUnknownTimeZone() { - return UNKNOWN_TIMEZONE; - } - - /** - * Returns a list of time zones to which a geocodable phone number belongs. - * - * @param number the phone number for which we want to get the time zones to which it belongs - * @return the list of corresponding time zones or a single element list with the default - * unknown time zone if no other time zone was found or if the number was invalid - */ - private List getTimeZonesForGeocodableNumber(PhoneNumber number) { - List timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number); - return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST - : timezones); - } - - /** - * Returns the list of time zones corresponding to the country calling code of {@code number}. - * - * @param number the phone number to look up - * @return the list of corresponding time zones or a single element list with the default - * unknown time zone if no other time zone was found - */ - private List getCountryLevelTimeZonesforNumber(PhoneNumber number) { - List timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number); - return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST - : timezones); + return com.google.i18n.phonenumbers.timezones.PhoneNumberToTimeZonesMapper.getUnknownTimeZone(); } } diff --git a/java/geocoder/src/com/google/i18n/phonenumbers/timezones/PhoneNumberToTimeZonesMapper.java b/java/geocoder/src/com/google/i18n/phonenumbers/timezones/PhoneNumberToTimeZonesMapper.java new file mode 100644 index 0000000000..872bd3a461 --- /dev/null +++ b/java/geocoder/src/com/google/i18n/phonenumbers/timezones/PhoneNumberToTimeZonesMapper.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012 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.timezones; + +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.prefixmapper.PrefixTimeZonesMap; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An offline mapper from phone numbers to time zones. + */ +public class PhoneNumberToTimeZonesMapper { + private static final String MAPPING_DATA_DIRECTORY = + "/com/google/i18n/phonenumbers/timezones/data/"; + private static final String MAPPING_DATA_FILE_NAME = "map_data"; + // This is defined by ICU as the unknown time zone. + private static final String UNKNOWN_TIMEZONE = "Etc/Unknown"; + // A list with the ICU unknown time zone as single element. + // @VisibleForTesting + static final List UNKNOWN_TIME_ZONE_LIST = new ArrayList(1); + static { + UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE); + } + + private static final Logger logger = + Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName()); + + private final PrefixTimeZonesMap prefixTimeZonesMap; + + // @VisibleForTesting + PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) { + this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile( + prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME); + } + + private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) { + this.prefixTimeZonesMap = prefixTimeZonesMap; + } + + private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) { + InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path); + ObjectInputStream in = null; + PrefixTimeZonesMap map = new PrefixTimeZonesMap(); + try { + in = new ObjectInputStream(source); + map.readExternal(in); + } catch (IOException e) { + logger.log(Level.WARNING, e.toString()); + } finally { + close(in); + } + return map; + } + + private static void close(InputStream in) { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + logger.log(Level.WARNING, e.toString()); + } + } + } + + /** + * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the + * map data in a thread-safe way. + */ + private static class LazyHolder { + private static final PhoneNumberToTimeZonesMapper INSTANCE; + static { + PrefixTimeZonesMap map = + loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME); + INSTANCE = new PhoneNumberToTimeZonesMapper(map); + } + } + + /** + * Gets a {@link PhoneNumberToTimeZonesMapper} instance. + * + *

The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling + * this method multiple times will only result in one instance being created. + * + * @return a {@link PhoneNumberToTimeZonesMapper} instance + */ + public static synchronized PhoneNumberToTimeZonesMapper getInstance() { + return LazyHolder.INSTANCE; + } + + /** + * Returns a list of time zones to which a phone number belongs. + * + *

This method assumes the validity of the number passed in has already been checked, and that + * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates + * for geo-localization. + * + * @param number a valid phone number for which we want to get the time zones to which it belongs + * @return a list of the corresponding time zones or a single element list with the default + * unknown time zone if no other time zone was found or if the number was invalid + */ + public List getTimeZonesForGeographicalNumber(PhoneNumber number) { + return getTimeZonesForGeocodableNumber(number); + } + + /** + * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks + * the validity of the number passed in. + * + * @param number the phone number for which we want to get the time zones to which it belongs + * @return a list of the corresponding time zones or a single element list with the default + * unknown time zone if no other time zone was found or if the number was invalid + */ + public List getTimeZonesForNumber(PhoneNumber number) { + PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number); + if (numberType == PhoneNumberType.UNKNOWN) { + return UNKNOWN_TIME_ZONE_LIST; + } else if (!PhoneNumberUtil.getInstance().isNumberGeographical( + numberType, number.getCountryCode())) { + return getCountryLevelTimeZonesforNumber(number); + } + return getTimeZonesForGeographicalNumber(number); + } + + /** + * Returns a String with the ICU unknown time zone. + */ + public static String getUnknownTimeZone() { + return UNKNOWN_TIMEZONE; + } + + /** + * Returns a list of time zones to which a geocodable phone number belongs. + * + * @param number the phone number for which we want to get the time zones to which it belongs + * @return the list of corresponding time zones or a single element list with the default + * unknown time zone if no other time zone was found or if the number was invalid + */ + private List getTimeZonesForGeocodableNumber(PhoneNumber number) { + List timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number); + return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST + : timezones); + } + + /** + * Returns the list of time zones corresponding to the country calling code of {@code number}. + * + * @param number the phone number to look up + * @return the list of corresponding time zones or a single element list with the default + * unknown time zone if no other time zone was found + */ + private List getCountryLevelTimeZonesforNumber(PhoneNumber number) { + List timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number); + return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST + : timezones); + } +} \ No newline at end of file diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java b/java/geocoder/test/com/google/i18n/phonenumbers/timezones/PhoneNumberToTimeZonesMapperTest.java similarity index 72% rename from java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java rename to java/geocoder/test/com/google/i18n/phonenumbers/timezones/PhoneNumberToTimeZonesMapperTest.java index 8bb3423da5..deeb7bc1e1 100644 --- a/java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java +++ b/java/geocoder/test/com/google/i18n/phonenumbers/timezones/PhoneNumberToTimeZonesMapperTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.i18n.phonenumbers; +package com.google.i18n.phonenumbers.timezones; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import junit.framework.TestCase; @@ -28,8 +28,10 @@ * @author Walter Erquinigo */ public class PhoneNumberToTimeZonesMapperTest extends TestCase { - private final PhoneNumberToTimeZonesMapper prefixTimeZonesMapper = - new PhoneNumberToTimeZonesMapper(TEST_MAPPING_DATA_DIRECTORY); + + private final com.google.i18n.phonenumbers.PhoneNumberToTimeZonesMapper prefixTimeZonesMapper = + new com.google.i18n.phonenumbers.PhoneNumberToTimeZonesMapper( + new PhoneNumberToTimeZonesMapper(TEST_MAPPING_DATA_DIRECTORY)); private static final String TEST_MAPPING_DATA_DIRECTORY = "/com/google/i18n/phonenumbers/timezones/testing_data/"; // Set up some test numbers to re-use. @@ -63,7 +65,7 @@ public class PhoneNumberToTimeZonesMapperTest extends TestCase { private static final String SEOUL_TZ = "Asia/Seoul"; private static final String SYDNEY_TZ = "Australia/Sydney"; - static List buildListOfTimeZones(String ... timezones) { + static List buildListOfTimeZones(String... timezones) { ArrayList timezonesList = new ArrayList(timezones.length); for (String timezone : timezones) { timezonesList.add(timezone); @@ -78,59 +80,59 @@ private static List getNanpaTimeZonesList() { public void testGetTimeZonesForNumber() { // Test with invalid numbers even when their country code prefixes exist in the mapper. assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST, - prefixTimeZonesMapper.getTimeZonesForNumber(US_INVALID_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForNumber(US_INVALID_NUMBER)); assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST, - prefixTimeZonesMapper.getTimeZonesForNumber(KO_INVALID_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForNumber(KO_INVALID_NUMBER)); // Test with valid prefixes. assertEquals(buildListOfTimeZones(SYDNEY_TZ), - prefixTimeZonesMapper.getTimeZonesForNumber(AU_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForNumber(AU_NUMBER)); assertEquals(buildListOfTimeZones(SEOUL_TZ), - prefixTimeZonesMapper.getTimeZonesForNumber(KO_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForNumber(KO_NUMBER)); assertEquals(buildListOfTimeZones(WINNIPEG_TZ), - prefixTimeZonesMapper.getTimeZonesForNumber(CA_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForNumber(CA_NUMBER)); assertEquals(buildListOfTimeZones(LOS_ANGELES_TZ), - prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER1)); + prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER1)); assertEquals(buildListOfTimeZones(NEW_YORK_TZ), - prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER2)); + prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER2)); // Test with an invalid country code. assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST, - prefixTimeZonesMapper.getTimeZonesForNumber(NUMBER_WITH_INVALID_COUNTRY_CODE)); + prefixTimeZonesMapper.getTimeZonesForNumber(NUMBER_WITH_INVALID_COUNTRY_CODE)); // Test with a non geographical phone number. assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST, - prefixTimeZonesMapper.getTimeZonesForNumber(INTERNATIONAL_TOLL_FREE)); + prefixTimeZonesMapper.getTimeZonesForNumber(INTERNATIONAL_TOLL_FREE)); } public void testGetTimeZonesForValidNumber() { // Test with invalid numbers even when their country code prefixes exist in the mapper. assertEquals(getNanpaTimeZonesList(), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_INVALID_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_INVALID_NUMBER)); assertEquals(buildListOfTimeZones(SEOUL_TZ), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(KO_INVALID_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(KO_INVALID_NUMBER)); // Test with valid prefixes. assertEquals(buildListOfTimeZones(SYDNEY_TZ), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(AU_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(AU_NUMBER)); assertEquals(buildListOfTimeZones(SEOUL_TZ), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(KO_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(KO_NUMBER)); assertEquals(buildListOfTimeZones(WINNIPEG_TZ), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(CA_NUMBER)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(CA_NUMBER)); assertEquals(buildListOfTimeZones(LOS_ANGELES_TZ), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_NUMBER1)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_NUMBER1)); assertEquals(buildListOfTimeZones(NEW_YORK_TZ), - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_NUMBER2)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_NUMBER2)); // Test with an invalid country code. assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST, - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber( - NUMBER_WITH_INVALID_COUNTRY_CODE)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber( + NUMBER_WITH_INVALID_COUNTRY_CODE)); // Test with a non geographical phone number. assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST, - prefixTimeZonesMapper.getTimeZonesForGeographicalNumber( - INTERNATIONAL_TOLL_FREE)); + prefixTimeZonesMapper.getTimeZonesForGeographicalNumber( + INTERNATIONAL_TOLL_FREE)); } public void testGetTimeZonesForValidNumberSearchingAtCountryCodeLevel() { // Test that the country level time zones are returned when the number passed in is valid but // not covered by any non-country level prefixes in the mapper. assertEquals(prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER3), - getNanpaTimeZonesList()); + getNanpaTimeZonesList()); } } diff --git a/pending_code_changes.txt b/pending_code_changes.txt index d67706acc0..5e7876ecb0 100644 --- a/pending_code_changes.txt +++ b/pending_code_changes.txt @@ -1,2 +1,3 @@ Code changes: - - Fixed a bug where the extension was appended twice in formatOutOfCountryKeepingAlphaChars in the Java version and updated FormatOutOfCountryKeepingAlphaChars in the C++ version to format the extension. \ No newline at end of file + - Fixed a bug where the extension was appended twice in formatOutOfCountryKeepingAlphaChars in the Java version and updated FormatOutOfCountryKeepingAlphaChars in the C++ version to format the extension. + - Deprecated com.google.phonenumbers versions of PhoneNumberToCarrierMapper and PhoneNumberToTimeZonesMapper in favour of counterparts in their respective packages. \ No newline at end of file