update changelog
[platform/upstream/libphonenumber.git] / java / geocoder / src / com / google / i18n / phonenumbers / geocoding / PhoneNumberOfflineGeocoder.java
1 /*
2  * Copyright (C) 2011 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.geocoding;
18
19 import com.google.i18n.phonenumbers.NumberParseException;
20 import com.google.i18n.phonenumbers.PhoneNumberUtil;
21 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
22 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
23 import com.google.i18n.phonenumbers.prefixmapper.PrefixFileReader;
24
25 import java.util.Locale;
26
27 /**
28  * An offline geocoder which provides geographical information related to a phone number.
29  *
30  * @author Shaopeng Jia
31  */
32 public class PhoneNumberOfflineGeocoder {
33   private static PhoneNumberOfflineGeocoder instance = null;
34   private static final String MAPPING_DATA_DIRECTORY =
35       "/com/google/i18n/phonenumbers/geocoding/data/";
36   private PrefixFileReader prefixFileReader = null;
37
38   private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
39
40   // @VisibleForTesting
41   PhoneNumberOfflineGeocoder(String phonePrefixDataDirectory) {
42     prefixFileReader = new PrefixFileReader(phonePrefixDataDirectory);
43   }
44
45   /**
46    * Gets a {@link PhoneNumberOfflineGeocoder} instance to carry out international phone number
47    * geocoding.
48    *
49    * <p> The {@link PhoneNumberOfflineGeocoder} is implemented as a singleton. Therefore, calling
50    * this method multiple times will only result in one instance being created.
51    *
52    * @return  a {@link PhoneNumberOfflineGeocoder} instance
53    */
54   public static synchronized PhoneNumberOfflineGeocoder getInstance() {
55     if (instance == null) {
56       instance = new PhoneNumberOfflineGeocoder(MAPPING_DATA_DIRECTORY);
57     }
58     return instance;
59   }
60
61   /**
62    * Returns the customary display name in the given language for the given territory the phone
63    * number is from.
64    */
65   private String getCountryNameForNumber(PhoneNumber number, Locale language) {
66     String regionCode = phoneUtil.getRegionCodeForNumber(number);
67     return getRegionDisplayName(regionCode, language);
68   }
69
70   /**
71    * Returns the customary display name in the given language for the given region.
72    */
73   private String getRegionDisplayName(String regionCode, Locale language) {
74     return (regionCode == null || regionCode.equals("ZZ") ||
75             regionCode.equals(PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY))
76         ? "" : new Locale("", regionCode).getDisplayCountry(language);
77   }
78
79   /**
80    * Returns a text description for the given phone number, in the language provided. The
81    * description might consist of the name of the country where the phone number is from, or the
82    * name of the geographical area the phone number is from if more detailed information is
83    * available.
84    *
85    * <p>This method assumes the validity of the number passed in has already been checked, and that
86    * the number is suitable for geocoding. We consider fixed-line and mobile numbers possible
87    * candidates for geocoding.
88    *
89    * @param number  a valid phone number for which we want to get a text description
90    * @param languageCode  the language code for which the description should be written
91    * @return  a text description for the given language code for the given phone number
92    */
93   public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode) {
94     String langStr = languageCode.getLanguage();
95     String scriptStr = "";  // No script is specified
96     String regionStr = languageCode.getCountry();
97
98     String areaDescription;
99     String mobileToken = PhoneNumberUtil.getCountryMobileToken(number.getCountryCode());
100     String nationalNumber = phoneUtil.getNationalSignificantNumber(number);
101     if (!mobileToken.equals("") && nationalNumber.startsWith(mobileToken)) {
102       // In some countries, eg. Argentina, mobile numbers have a mobile token before the national
103       // destination code, this should be removed before geocoding.
104       nationalNumber = nationalNumber.substring(mobileToken.length());
105       String region = phoneUtil.getRegionCodeForCountryCode(number.getCountryCode());
106       PhoneNumber copiedNumber;
107       try {
108         copiedNumber = phoneUtil.parse(nationalNumber, region);
109       } catch (NumberParseException e) {
110         // If this happens, just reuse what we had.
111         copiedNumber = number;
112       }
113       areaDescription = prefixFileReader.getDescriptionForNumber(copiedNumber, langStr, scriptStr,
114                                                                  regionStr);
115     } else {
116       areaDescription = prefixFileReader.getDescriptionForNumber(number, langStr, scriptStr,
117                                                                  regionStr);
118     }
119     return (areaDescription.length() > 0)
120         ? areaDescription : getCountryNameForNumber(number, languageCode);
121   }
122
123   /**
124    * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but also considers the
125    * region of the user. If the phone number is from the same region as the user, only a lower-level
126    * description will be returned, if one exists. Otherwise, the phone number's region will be
127    * returned, with optionally some more detailed information.
128    *
129    * <p>For example, for a user from the region "US" (United States), we would show "Mountain View,
130    * CA" for a particular number, omitting the United States from the description. For a user from
131    * the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United
132    * States" or even just "United States".
133    *
134    * <p>This method assumes the validity of the number passed in has already been checked.
135    *
136    * @param number  the phone number for which we want to get a text description
137    * @param languageCode  the language code for which the description should be written
138    * @param userRegion  the region code for a given user. This region will be omitted from the
139    *     description if the phone number comes from this region. It is a two-letter uppercase ISO
140    *     country code as defined by ISO 3166-1.
141    * @return  a text description for the given language code for the given phone number, or empty
142    *     string if the number passed in is invalid
143    */
144   public String getDescriptionForValidNumber(PhoneNumber number, Locale languageCode,
145                                              String userRegion) {
146     // If the user region matches the number's region, then we just show the lower-level
147     // description, if one exists - if no description exists, we will show the region(country) name
148     // for the number.
149     String regionCode = phoneUtil.getRegionCodeForNumber(number);
150     if (userRegion.equals(regionCode)) {
151       return getDescriptionForValidNumber(number, languageCode);
152     }
153     // Otherwise, we just show the region(country) name for now.
154     return getRegionDisplayName(regionCode, languageCode);
155     // TODO: Concatenate the lower-level and country-name information in an appropriate
156     // way for each language.
157   }
158
159   /**
160    * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale)} but explicitly checks
161    * the validity of the number passed in.
162    *
163    * @param number  the phone number for which we want to get a text description
164    * @param languageCode  the language code for which the description should be written
165    * @return  a text description for the given language code for the given phone number, or empty
166    *     string if the number passed in is invalid
167    */
168   public String getDescriptionForNumber(PhoneNumber number, Locale languageCode) {
169     PhoneNumberType numberType = phoneUtil.getNumberType(number);
170     if (numberType == PhoneNumberType.UNKNOWN) {
171       return "";
172     } else if (!canBeGeocoded(numberType)) {
173       return getCountryNameForNumber(number, languageCode);
174     }
175     return getDescriptionForValidNumber(number, languageCode);
176   }
177
178   /**
179    * As per {@link #getDescriptionForValidNumber(PhoneNumber, Locale, String)} but
180    * explicitly checks the validity of the number passed in.
181    *
182    * @param number  the phone number for which we want to get a text description
183    * @param languageCode  the language code for which the description should be written
184    * @param userRegion  the region code for a given user. This region will be omitted from the
185    *     description if the phone number comes from this region. It is a two-letter uppercase ISO
186    *     country code as defined by ISO 3166-1.
187    * @return  a text description for the given language code for the given phone number, or empty
188    *     string if the number passed in is invalid
189    */
190   public String getDescriptionForNumber(PhoneNumber number, Locale languageCode,
191                                         String userRegion) {
192     PhoneNumberType numberType = phoneUtil.getNumberType(number);
193     if (numberType == PhoneNumberType.UNKNOWN) {
194       return "";
195     } else if (!canBeGeocoded(numberType)) {
196       return getCountryNameForNumber(number, languageCode);
197     }
198     return getDescriptionForValidNumber(number, languageCode, userRegion);
199   }
200
201   /**
202    * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
203    * stricter check, as it determines if a number has a geographical association. Also, if new
204    * phone number types were added, we should check if this other method should be updated too.
205    */
206   private boolean canBeGeocoded(PhoneNumberType numberType) {
207     return (numberType == PhoneNumberType.FIXED_LINE ||
208             numberType == PhoneNumberType.MOBILE ||
209             numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
210   }
211 }