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