JAVA: Added the phone number to timezones mapper, including tests, binary generation...
[platform/upstream/libphonenumber.git] / java / internal / prefixmapper / src / com / google / i18n / phonenumbers / prefixmapper / PhonePrefixMap.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.prefixmapper;
18
19 import com.google.i18n.phonenumbers.PhoneNumberUtil;
20 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.Externalizable;
24 import java.io.IOException;
25 import java.io.ObjectInput;
26 import java.io.ObjectOutput;
27 import java.io.ObjectOutputStream;
28 import java.util.SortedMap;
29 import java.util.SortedSet;
30 import java.util.logging.Logger;
31
32 /**
33  * A utility that maps phone number prefixes to a description string, which may be, for example,
34  * the geographical area the prefix covers.
35  *
36  * @author Shaopeng Jia
37  */
38 public class PhonePrefixMap implements Externalizable {
39   private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
40   private static final Logger LOGGER = Logger.getLogger(PhonePrefixMap.class.getName());
41
42   private PhonePrefixMapStorageStrategy phonePrefixMapStorage;
43
44   // @VisibleForTesting
45   PhonePrefixMapStorageStrategy getPhonePrefixMapStorage() {
46     return phonePrefixMapStorage;
47   }
48
49   /**
50    * Creates an empty {@link PhonePrefixMap}. The default constructor is necessary for implementing
51    * {@link Externalizable}. The empty map could later be populated by
52    * {@link #readPhonePrefixMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}.
53    */
54   public PhonePrefixMap() {}
55
56   /**
57    * Gets the size of the provided phone prefix map storage. The map storage passed-in will be
58    * filled as a result.
59    */
60   private static int getSizeOfPhonePrefixMapStorage(PhonePrefixMapStorageStrategy mapStorage,
61       SortedMap<Integer, String> phonePrefixMap) throws IOException {
62     mapStorage.readFromSortedMap(phonePrefixMap);
63     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
64     ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
65     mapStorage.writeExternal(objectOutputStream);
66     objectOutputStream.flush();
67     int sizeOfStorage = byteArrayOutputStream.size();
68     objectOutputStream.close();
69     return sizeOfStorage;
70   }
71
72   private PhonePrefixMapStorageStrategy createDefaultMapStorage() {
73     return new DefaultMapStorage();
74   }
75
76   private PhonePrefixMapStorageStrategy createFlyweightMapStorage() {
77     return new FlyweightMapStorage();
78   }
79
80   /**
81    * Gets the smaller phone prefix map storage strategy according to the provided phone prefix map.
82    * It actually uses (outputs the data to a stream) both strategies and retains the best one which
83    * make this method quite expensive.
84    */
85   // @VisibleForTesting
86   PhonePrefixMapStorageStrategy getSmallerMapStorage(SortedMap<Integer, String> phonePrefixMap) {
87     try {
88       PhonePrefixMapStorageStrategy flyweightMapStorage = createFlyweightMapStorage();
89       int sizeOfFlyweightMapStorage = getSizeOfPhonePrefixMapStorage(flyweightMapStorage,
90                                                                      phonePrefixMap);
91
92       PhonePrefixMapStorageStrategy defaultMapStorage = createDefaultMapStorage();
93       int sizeOfDefaultMapStorage = getSizeOfPhonePrefixMapStorage(defaultMapStorage,
94                                                                    phonePrefixMap);
95
96       return sizeOfFlyweightMapStorage < sizeOfDefaultMapStorage
97           ? flyweightMapStorage : defaultMapStorage;
98     } catch (IOException e) {
99       LOGGER.severe(e.getMessage());
100       return createFlyweightMapStorage();
101     }
102   }
103
104   /**
105    * Creates an {@link PhonePrefixMap} initialized with {@code sortedPhonePrefixMap}.  Note that the
106    * underlying implementation of this method is expensive thus should not be called by
107    * time-critical applications.
108    *
109    * @param sortedPhonePrefixMap  a map from phone number prefixes to descriptions of those prefixes
110    * sorted in ascending order of the phone number prefixes as integers.
111    */
112   public void readPhonePrefixMap(SortedMap<Integer, String> sortedPhonePrefixMap) {
113     phonePrefixMapStorage = getSmallerMapStorage(sortedPhonePrefixMap);
114   }
115
116   /**
117    * Supports Java Serialization.
118    */
119   public void readExternal(ObjectInput objectInput) throws IOException {
120     // Read the phone prefix map storage strategy flag.
121     boolean useFlyweightMapStorage = objectInput.readBoolean();
122     if (useFlyweightMapStorage) {
123       phonePrefixMapStorage = new FlyweightMapStorage();
124     } else {
125       phonePrefixMapStorage = new DefaultMapStorage();
126     }
127     phonePrefixMapStorage.readExternal(objectInput);
128   }
129
130   /**
131    * Supports Java Serialization.
132    */
133   public void writeExternal(ObjectOutput objectOutput) throws IOException {
134     objectOutput.writeBoolean(phonePrefixMapStorage instanceof FlyweightMapStorage);
135     phonePrefixMapStorage.writeExternal(objectOutput);
136   }
137
138   /**
139    * Returns the description of the {@code number}. This method distinguishes the case of an invalid
140    * prefix and a prefix for which the name is not available in the current language. If the
141    * description is not available in the current language an empty string is returned. If no
142    * description was found for the provided number, null is returned.
143    *
144    * @param number  the phone number to look up
145    * @return  the description of the number
146    */
147   String lookup(long number) {
148     int numOfEntries = phonePrefixMapStorage.getNumOfEntries();
149     if (numOfEntries == 0) {
150       return null;
151     }
152     long phonePrefix = number;
153     int currentIndex = numOfEntries - 1;
154     SortedSet<Integer> currentSetOfLengths = phonePrefixMapStorage.getPossibleLengths();
155     while (currentSetOfLengths.size() > 0) {
156       Integer possibleLength = currentSetOfLengths.last();
157       String phonePrefixStr = String.valueOf(phonePrefix);
158       if (phonePrefixStr.length() > possibleLength) {
159         phonePrefix = Long.parseLong(phonePrefixStr.substring(0, possibleLength));
160       }
161       currentIndex = binarySearch(0, currentIndex, phonePrefix);
162       if (currentIndex < 0) {
163         return null;
164       }
165       int currentPrefix = phonePrefixMapStorage.getPrefix(currentIndex);
166       if (phonePrefix == currentPrefix) {
167         return phonePrefixMapStorage.getDescription(currentIndex);
168       }
169       currentSetOfLengths = currentSetOfLengths.headSet(possibleLength);
170     }
171     return null;
172   }
173
174   /**
175    * As per {@link #lookup(long)}, but receives the number as a PhoneNumber instead of a long.
176    *
177    * @param number  the phone number to look up
178    * @return  the description corresponding to the prefix that best matches this phone number
179    */
180   public String lookup(PhoneNumber number) {
181     long phonePrefix =
182         Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
183     return lookup(phonePrefix);
184   }
185
186   /**
187    * Does a binary search for {@code value} in the provided array from {@code start} to {@code end}
188    * (inclusive). Returns the position if {@code value} is found; otherwise, returns the
189    * position which has the largest value that is less than {@code value}. This means if
190    * {@code value} is the smallest, -1 will be returned.
191    */
192   private int binarySearch(int start, int end, long value) {
193     int current = 0;
194     while (start <= end) {
195       current = (start + end) >>> 1;
196       int currentValue = phonePrefixMapStorage.getPrefix(current);
197       if (currentValue == value) {
198         return current;
199       } else if (currentValue > value) {
200         current--;
201         end = current;
202       } else {
203         start = current + 1;
204       }
205     }
206     return current;
207   }
208
209   /**
210    * Dumps the mappings contained in the phone prefix map.
211    */
212   @Override
213   public String toString() {
214     return phonePrefixMapStorage.toString();
215   }
216 }