2 * Copyright (C) 2011 The Libphonenumber Authors
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.i18n.phonenumbers.prefixmapper;
19 import com.google.i18n.phonenumbers.PhoneNumberUtil;
20 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
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;
33 * A utility that maps phone number prefixes to a description string, which may be, for example,
34 * the geographical area the prefix covers.
36 * @author Shaopeng Jia
38 public class PhonePrefixMap implements Externalizable {
39 private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
40 private static final Logger LOGGER = Logger.getLogger(PhonePrefixMap.class.getName());
42 private PhonePrefixMapStorageStrategy phonePrefixMapStorage;
45 PhonePrefixMapStorageStrategy getPhonePrefixMapStorage() {
46 return phonePrefixMapStorage;
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)}.
54 public PhonePrefixMap() {}
57 * Gets the size of the provided phone prefix map storage. The map storage passed-in will be
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();
72 private PhonePrefixMapStorageStrategy createDefaultMapStorage() {
73 return new DefaultMapStorage();
76 private PhonePrefixMapStorageStrategy createFlyweightMapStorage() {
77 return new FlyweightMapStorage();
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.
86 PhonePrefixMapStorageStrategy getSmallerMapStorage(SortedMap<Integer, String> phonePrefixMap) {
88 PhonePrefixMapStorageStrategy flyweightMapStorage = createFlyweightMapStorage();
89 int sizeOfFlyweightMapStorage = getSizeOfPhonePrefixMapStorage(flyweightMapStorage,
92 PhonePrefixMapStorageStrategy defaultMapStorage = createDefaultMapStorage();
93 int sizeOfDefaultMapStorage = getSizeOfPhonePrefixMapStorage(defaultMapStorage,
96 return sizeOfFlyweightMapStorage < sizeOfDefaultMapStorage
97 ? flyweightMapStorage : defaultMapStorage;
98 } catch (IOException e) {
99 LOGGER.severe(e.getMessage());
100 return createFlyweightMapStorage();
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.
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.
112 public void readPhonePrefixMap(SortedMap<Integer, String> sortedPhonePrefixMap) {
113 phonePrefixMapStorage = getSmallerMapStorage(sortedPhonePrefixMap);
117 * Supports Java Serialization.
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();
125 phonePrefixMapStorage = new DefaultMapStorage();
127 phonePrefixMapStorage.readExternal(objectInput);
131 * Supports Java Serialization.
133 public void writeExternal(ObjectOutput objectOutput) throws IOException {
134 objectOutput.writeBoolean(phonePrefixMapStorage instanceof FlyweightMapStorage);
135 phonePrefixMapStorage.writeExternal(objectOutput);
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.
144 * @param number the phone number to look up
145 * @return the description of the number
147 String lookup(long number) {
148 int numOfEntries = phonePrefixMapStorage.getNumOfEntries();
149 if (numOfEntries == 0) {
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));
161 currentIndex = binarySearch(0, currentIndex, phonePrefix);
162 if (currentIndex < 0) {
165 int currentPrefix = phonePrefixMapStorage.getPrefix(currentIndex);
166 if (phonePrefix == currentPrefix) {
167 return phonePrefixMapStorage.getDescription(currentIndex);
169 currentSetOfLengths = currentSetOfLengths.headSet(possibleLength);
175 * As per {@link #lookup(long)}, but receives the number as a PhoneNumber instead of a long.
177 * @param number the phone number to look up
178 * @return the description corresponding to the prefix that best matches this phone number
180 public String lookup(PhoneNumber number) {
182 Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
183 return lookup(phonePrefix);
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.
192 private int binarySearch(int start, int end, long value) {
194 while (start <= end) {
195 current = (start + end) >>> 1;
196 int currentValue = phonePrefixMapStorage.getPrefix(current);
197 if (currentValue == value) {
199 } else if (currentValue > value) {
210 * Dumps the mappings contained in the phone prefix map.
213 public String toString() {
214 return phonePrefixMapStorage.toString();