JAVA/JS/CPP: v7.0.2 metadata updates.
[platform/upstream/libphonenumber.git] / java / geocoder / src / com / google / i18n / phonenumbers / PhoneNumberToTimeZonesMapper.java
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 PhoneNumberToTimeZonesMapper instance = null;
37   private static final String MAPPING_DATA_DIRECTORY =
38       "/com/google/i18n/phonenumbers/timezones/data/";
39   private static final String MAPPING_DATA_FILE_NAME = "map_data";
40   // This is defined by ICU as the unknown time zone.
41   private static final String UNKNOWN_TIMEZONE = "Etc/Unknown";
42   // A list with the ICU unknown time zone as single element.
43   // @VisibleForTesting
44   static final List<String> UNKNOWN_TIME_ZONE_LIST = new ArrayList<String>(1);
45   static {
46     UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE);
47   }
48
49   private static final Logger LOGGER =
50       Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName());
51
52   private PrefixTimeZonesMap prefixTimeZonesMap = null;
53
54   // @VisibleForTesting
55   PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) {
56     this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile(
57         prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME);
58   }
59
60   private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) {
61     this.prefixTimeZonesMap = prefixTimeZonesMap;
62   }
63
64   private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) {
65     InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path);
66     ObjectInputStream in = null;
67     PrefixTimeZonesMap map = new PrefixTimeZonesMap();
68     try {
69       in = new ObjectInputStream(source);
70       map.readExternal(in);
71     } catch (IOException e) {
72       LOGGER.log(Level.WARNING, e.toString());
73     } finally {
74       close(in);
75     }
76     return map;
77   }
78
79   private static void close(InputStream in) {
80     if (in != null) {
81       try {
82         in.close();
83       } catch (IOException e) {
84         LOGGER.log(Level.WARNING, e.toString());
85       }
86     }
87   }
88
89   /**
90    * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the
91    * map data in a thread-safe way.
92    */
93   private static class LazyHolder {
94     private static final PhoneNumberToTimeZonesMapper INSTANCE;
95     static {
96       PrefixTimeZonesMap map =
97           loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME);
98       INSTANCE = new PhoneNumberToTimeZonesMapper(map);
99     }
100   }
101
102   /**
103    * Gets a {@link PhoneNumberToTimeZonesMapper} instance.
104    *
105    * <p> The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling
106    * this method multiple times will only result in one instance being created.
107    *
108    * @return  a {@link PhoneNumberToTimeZonesMapper} instance
109    */
110   public static synchronized PhoneNumberToTimeZonesMapper getInstance() {
111     return LazyHolder.INSTANCE;
112   }
113
114   /**
115    * Returns a list of time zones to which a phone number belongs.
116    *
117    * <p>This method assumes the validity of the number passed in has already been checked, and that
118    * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates
119    * for geo-localization.
120    *
121    * @param number  a valid phone number for which we want to get the time zones to which it belongs
122    * @return  a list of the corresponding time zones or a single element list with the default
123    *     unknown time zone if no other time zone was found or if the number was invalid
124    */
125   public List<String> getTimeZonesForGeographicalNumber(PhoneNumber number) {
126     return getTimeZonesForGeocodableNumber(number);
127   }
128
129   /**
130    * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks
131    * the validity of the number passed in.
132    *
133    * @param number  the phone number for which we want to get the time zones to which it belongs
134    * @return  a list of the corresponding time zones or a single element list with the default
135    *     unknown time zone if no other time zone was found or if the number was invalid
136    */
137   public List<String> getTimeZonesForNumber(PhoneNumber number) {
138     PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number);
139     if (numberType == PhoneNumberType.UNKNOWN) {
140       return UNKNOWN_TIME_ZONE_LIST;
141     } else if (!canBeGeocoded(numberType)) {
142       return getCountryLevelTimeZonesforNumber(number);
143     }
144     return getTimeZonesForGeographicalNumber(number);
145   }
146
147   /**
148    * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
149    * stricter check, as it determines if a number has a geographical association. Also, if new
150    * phone number types were added, we should check if this other method should be updated too.
151    * TODO: Remove duplication by completing the logic in the method in PhoneNumberUtil.
152    *                   For more information, see the comments in that method.
153    */
154   private boolean canBeGeocoded(PhoneNumberType numberType) {
155     return (numberType == PhoneNumberType.FIXED_LINE ||
156             numberType == PhoneNumberType.MOBILE ||
157             numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
158   }
159
160   /**
161    * Returns a String with the ICU unknown time zone.
162    */
163   public static String getUnknownTimeZone() {
164     return UNKNOWN_TIMEZONE;
165   }
166
167   /**
168    * Returns a list of time zones to which a geocodable phone number belongs.
169    *
170    * @param number  the phone number for which we want to get the time zones to which it belongs
171    * @return  the list of corresponding  time zones or a single element list with the default
172    *     unknown time zone if no other time zone was found or if the number was invalid
173    */
174   private List<String> getTimeZonesForGeocodableNumber(PhoneNumber number) {
175     List<String> timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number);
176     return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
177                                                             : timezones);
178   }
179
180   /**
181    * Returns the list of time zones corresponding to the country calling code of {@code number}.
182    *
183    * @param number  the phone number to look up
184    * @return  the list of corresponding time zones or a single element list with the default
185    *     unknown time zone if no other time zone was found
186    */
187   private List<String> getCountryLevelTimeZonesforNumber(PhoneNumber number) {
188     List<String> timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number);
189     return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
190                                                             : timezones);
191   }
192 }