• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.android.i18n.phonenumbers.geocoding;
18 
19 import com.android.i18n.phonenumbers.PhoneNumberUtil;
20 import com.android.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 string describing the geographical area the prefix
34  * covers.
35  *
36  * @author Shaopeng Jia
37  */
38 public class AreaCodeMap implements Externalizable {
39   private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
40   private static final Logger LOGGER = Logger.getLogger(AreaCodeMap.class.getName());
41 
42   private AreaCodeMapStorageStrategy areaCodeMapStorage;
43 
44   // @VisibleForTesting
getAreaCodeMapStorage()45   AreaCodeMapStorageStrategy getAreaCodeMapStorage() {
46     return areaCodeMapStorage;
47   }
48 
49   /**
50    * Creates an empty {@link AreaCodeMap}. The default constructor is necessary for implementing
51    * {@link Externalizable}. The empty map could later be populated by
52    * {@link #readAreaCodeMap(java.util.SortedMap)} or {@link #readExternal(java.io.ObjectInput)}.
53    */
AreaCodeMap()54   public AreaCodeMap() {}
55 
56   /**
57    * Gets the size of the provided area code map storage. The map storage passed-in will be filled
58    * as a result.
59    */
getSizeOfAreaCodeMapStorage(AreaCodeMapStorageStrategy mapStorage, SortedMap<Integer, String> areaCodeMap)60   private static int getSizeOfAreaCodeMapStorage(AreaCodeMapStorageStrategy mapStorage,
61       SortedMap<Integer, String> areaCodeMap) throws IOException {
62     mapStorage.readFromSortedMap(areaCodeMap);
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 
createDefaultMapStorage()72   private AreaCodeMapStorageStrategy createDefaultMapStorage() {
73     return new DefaultMapStorage();
74   }
75 
createFlyweightMapStorage()76   private AreaCodeMapStorageStrategy createFlyweightMapStorage() {
77     return new FlyweightMapStorage();
78   }
79 
80   /**
81    * Gets the smaller area code map storage strategy according to the provided area code map. It
82    * 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
getSmallerMapStorage(SortedMap<Integer, String> areaCodeMap)86   AreaCodeMapStorageStrategy getSmallerMapStorage(SortedMap<Integer, String> areaCodeMap) {
87     try {
88       AreaCodeMapStorageStrategy flyweightMapStorage = createFlyweightMapStorage();
89       int sizeOfFlyweightMapStorage = getSizeOfAreaCodeMapStorage(flyweightMapStorage, areaCodeMap);
90 
91       AreaCodeMapStorageStrategy defaultMapStorage = createDefaultMapStorage();
92       int sizeOfDefaultMapStorage = getSizeOfAreaCodeMapStorage(defaultMapStorage, areaCodeMap);
93 
94       return sizeOfFlyweightMapStorage < sizeOfDefaultMapStorage
95           ? flyweightMapStorage : defaultMapStorage;
96     } catch (IOException e) {
97       LOGGER.severe(e.getMessage());
98       return createFlyweightMapStorage();
99     }
100   }
101 
102   /**
103    * Creates an {@link AreaCodeMap} initialized with {@code sortedAreaCodeMap}.  Note that the
104    * underlying implementation of this method is expensive thus should not be called by
105    * time-critical applications.
106    *
107    * @param sortedAreaCodeMap  a map from phone number prefixes to descriptions of corresponding
108    *     geographical areas, sorted in ascending order of the phone number prefixes as integers.
109    */
readAreaCodeMap(SortedMap<Integer, String> sortedAreaCodeMap)110   public void readAreaCodeMap(SortedMap<Integer, String> sortedAreaCodeMap) {
111     areaCodeMapStorage = getSmallerMapStorage(sortedAreaCodeMap);
112   }
113 
114   /**
115    * Supports Java Serialization.
116    */
readExternal(ObjectInput objectInput)117   public void readExternal(ObjectInput objectInput) throws IOException {
118     // Read the area code map storage strategy flag.
119     boolean useFlyweightMapStorage = objectInput.readBoolean();
120     if (useFlyweightMapStorage) {
121       areaCodeMapStorage = new FlyweightMapStorage();
122     } else {
123       areaCodeMapStorage = new DefaultMapStorage();
124     }
125     areaCodeMapStorage.readExternal(objectInput);
126   }
127 
128   /**
129    * Supports Java Serialization.
130    */
writeExternal(ObjectOutput objectOutput)131   public void writeExternal(ObjectOutput objectOutput) throws IOException {
132     objectOutput.writeBoolean(areaCodeMapStorage instanceof FlyweightMapStorage);
133     areaCodeMapStorage.writeExternal(objectOutput);
134   }
135 
136   /**
137    * Returns the description of the geographical area the {@code number} corresponds to. This method
138    * distinguishes the case of an invalid prefix and a prefix for which the name is not available in
139    * the current language. If the description is not available in the current language an empty
140    * string is returned. If no description was found for the provided number, null is returned.
141    *
142    * @param number  the phone number to look up
143    * @return  the description of the geographical area
144    */
lookup(PhoneNumber number)145   String lookup(PhoneNumber number) {
146     int numOfEntries = areaCodeMapStorage.getNumOfEntries();
147     if (numOfEntries == 0) {
148       return null;
149     }
150     long phonePrefix =
151         Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
152     int currentIndex = numOfEntries - 1;
153     SortedSet<Integer> currentSetOfLengths = areaCodeMapStorage.getPossibleLengths();
154     while (currentSetOfLengths.size() > 0) {
155       Integer possibleLength = currentSetOfLengths.last();
156       String phonePrefixStr = String.valueOf(phonePrefix);
157       if (phonePrefixStr.length() > possibleLength) {
158         phonePrefix = Long.parseLong(phonePrefixStr.substring(0, possibleLength));
159       }
160       currentIndex = binarySearch(0, currentIndex, phonePrefix);
161       if (currentIndex < 0) {
162         return null;
163       }
164       int currentPrefix = areaCodeMapStorage.getPrefix(currentIndex);
165       if (phonePrefix == currentPrefix) {
166         return areaCodeMapStorage.getDescription(currentIndex);
167       }
168       currentSetOfLengths = currentSetOfLengths.headSet(possibleLength);
169     }
170     return null;
171   }
172 
173   /**
174    * Does a binary search for {@code value} in the provided array from {@code start} to {@code end}
175    * (inclusive). Returns the position if {@code value} is found; otherwise, returns the
176    * position which has the largest value that is less than {@code value}. This means if
177    * {@code value} is the smallest, -1 will be returned.
178    */
binarySearch(int start, int end, long value)179   private int binarySearch(int start, int end, long value) {
180     int current = 0;
181     while (start <= end) {
182       current = (start + end) >>> 1;
183       int currentValue = areaCodeMapStorage.getPrefix(current);
184       if (currentValue == value) {
185         return current;
186       } else if (currentValue > value) {
187         current--;
188         end = current;
189       } else {
190         start = current + 1;
191       }
192     }
193     return current;
194   }
195 
196   /**
197    * Dumps the mappings contained in the area code map.
198    */
199   @Override
toString()200   public String toString() {
201     return areaCodeMapStorage.toString();
202   }
203 }
204