Home | History | Annotate | Download | only in phonenumbers
      1 /*
      2  * Copyright (C) 2012 The Libphonenumber Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.i18n.phonenumbers;
     18 
     19 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
     20 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
     21 import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
     22 
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.io.ObjectInputStream;
     26 import java.util.ArrayList;
     27 import java.util.Collections;
     28 import java.util.List;
     29 import java.util.logging.Level;
     30 import java.util.logging.Logger;
     31 
     32 /**
     33  * An offline mapper from phone numbers to time zones.
     34  */
     35 public class PhoneNumberToTimeZonesMapper {
     36   private static final String MAPPING_DATA_DIRECTORY =
     37       "/com/google/i18n/phonenumbers/timezones/data/";
     38   private static final String MAPPING_DATA_FILE_NAME = "map_data";
     39   // This is defined by ICU as the unknown time zone.
     40   private static final String UNKNOWN_TIMEZONE = "Etc/Unknown";
     41   // A list with the ICU unknown time zone as single element.
     42   // @VisibleForTesting
     43   static final List<String> UNKNOWN_TIME_ZONE_LIST = new ArrayList<String>(1);
     44   static {
     45     UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE);
     46   }
     47 
     48   private static final Logger LOGGER =
     49       Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName());
     50 
     51   private PrefixTimeZonesMap prefixTimeZonesMap = null;
     52 
     53   // @VisibleForTesting
     54   PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) {
     55     this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile(
     56         prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME);
     57   }
     58 
     59   private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) {
     60     this.prefixTimeZonesMap = prefixTimeZonesMap;
     61   }
     62 
     63   private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) {
     64     InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path);
     65     ObjectInputStream in = null;
     66     PrefixTimeZonesMap map = new PrefixTimeZonesMap();
     67     try {
     68       in = new ObjectInputStream(source);
     69       map.readExternal(in);
     70     } catch (IOException e) {
     71       LOGGER.log(Level.WARNING, e.toString());
     72     } finally {
     73       close(in);
     74     }
     75     return map;
     76   }
     77 
     78   private static void close(InputStream in) {
     79     if (in != null) {
     80       try {
     81         in.close();
     82       } catch (IOException e) {
     83         LOGGER.log(Level.WARNING, e.toString());
     84       }
     85     }
     86   }
     87 
     88   /**
     89    * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the
     90    * map data in a thread-safe way.
     91    */
     92   private static class LazyHolder {
     93     private static final PhoneNumberToTimeZonesMapper INSTANCE;
     94     static {
     95       PrefixTimeZonesMap map =
     96           loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME);
     97       INSTANCE = new PhoneNumberToTimeZonesMapper(map);
     98     }
     99   }
    100 
    101   /**
    102    * Gets a {@link PhoneNumberToTimeZonesMapper} instance.
    103    *
    104    * <p> The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling
    105    * this method multiple times will only result in one instance being created.
    106    *
    107    * @return  a {@link PhoneNumberToTimeZonesMapper} instance
    108    */
    109   public static synchronized PhoneNumberToTimeZonesMapper getInstance() {
    110     return LazyHolder.INSTANCE;
    111   }
    112 
    113   /**
    114    * Returns a list of time zones to which a phone number belongs.
    115    *
    116    * <p>This method assumes the validity of the number passed in has already been checked, and that
    117    * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates
    118    * for geo-localization.
    119    *
    120    * @param number  a valid phone number for which we want to get the time zones to which it belongs
    121    * @return  a list of the corresponding time zones or a single element list with the default
    122    *     unknown time zone if no other time zone was found or if the number was invalid
    123    */
    124   public List<String> getTimeZonesForGeographicalNumber(PhoneNumber number) {
    125     return getTimeZonesForGeocodableNumber(number);
    126   }
    127 
    128   /**
    129    * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks
    130    * the validity of the number passed in.
    131    *
    132    * @param number  the phone number for which we want to get the time zones to which it belongs
    133    * @return  a list of the corresponding time zones or a single element list with the default
    134    *     unknown time zone if no other time zone was found or if the number was invalid
    135    */
    136   public List<String> getTimeZonesForNumber(PhoneNumber number) {
    137     PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number);
    138     if (numberType == PhoneNumberType.UNKNOWN) {
    139       return UNKNOWN_TIME_ZONE_LIST;
    140     } else if (!canBeGeocoded(numberType)) {
    141       return getCountryLevelTimeZonesforNumber(number);
    142     }
    143     return getTimeZonesForGeographicalNumber(number);
    144   }
    145 
    146   /**
    147    * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
    148    * stricter check, as it determines if a number has a geographical association. Also, if new
    149    * phone number types were added, we should check if this other method should be updated too.
    150    * TODO: Remove duplication by completing the logic in the method in PhoneNumberUtil.
    151    *                   For more information, see the comments in that method.
    152    */
    153   private boolean canBeGeocoded(PhoneNumberType numberType) {
    154     return (numberType == PhoneNumberType.FIXED_LINE ||
    155             numberType == PhoneNumberType.MOBILE ||
    156             numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
    157   }
    158 
    159   /**
    160    * Returns a String with the ICU unknown time zone.
    161    */
    162   public static String getUnknownTimeZone() {
    163     return UNKNOWN_TIMEZONE;
    164   }
    165 
    166   /**
    167    * Returns a list of time zones to which a geocodable phone number belongs.
    168    *
    169    * @param number  the phone number for which we want to get the time zones to which it belongs
    170    * @return  the list of corresponding  time zones or a single element list with the default
    171    *     unknown time zone if no other time zone was found or if the number was invalid
    172    */
    173   private List<String> getTimeZonesForGeocodableNumber(PhoneNumber number) {
    174     List<String> timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number);
    175     return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
    176                                                             : timezones);
    177   }
    178 
    179   /**
    180    * Returns the list of time zones corresponding to the country calling code of {@code number}.
    181    *
    182    * @param number  the phone number to look up
    183    * @return  the list of corresponding time zones or a single element list with the default
    184    *     unknown time zone if no other time zone was found
    185    */
    186   private List<String> getCountryLevelTimeZonesforNumber(PhoneNumber number) {
    187     List<String> timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number);
    188     return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
    189                                                             : timezones);
    190   }
    191 }
    192