• 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 java.io.IOException;
20 import java.io.ObjectInput;
21 import java.io.ObjectOutput;
22 import java.nio.ByteBuffer;
23 import java.util.Arrays;
24 import java.util.Map.Entry;
25 import java.util.SortedMap;
26 import java.util.SortedSet;
27 import java.util.TreeSet;
28 
29 /**
30  * Flyweight area code map storage strategy that uses a table to store unique strings and shorts to
31  * store the prefix and description indexes when possible. It is particularly space-efficient when
32  * the provided area code map contains a lot of redundant descriptions.
33  *
34  * @author Philippe Liard
35  */
36 final class FlyweightMapStorage extends AreaCodeMapStorageStrategy {
37   // Size of short and integer types in bytes.
38   private static final int SHORT_NUM_BYTES = Short.SIZE / 8;
39   private static final int INT_NUM_BYTES = Integer.SIZE / 8;
40 
41   // The number of bytes used to store a phone number prefix.
42   private int prefixSizeInBytes;
43   // The number of bytes used to store a description index. It is computed from the size of the
44   // description pool containing all the strings.
45   private int descIndexSizeInBytes;
46 
47   private ByteBuffer phoneNumberPrefixes;
48   private ByteBuffer descriptionIndexes;
49 
50   // Sorted string array of unique description strings.
51   private String[] descriptionPool;
52 
53   @Override
getPrefix(int index)54   public int getPrefix(int index) {
55     return readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, index);
56   }
57 
58   /**
59    * This implementation returns the same string (same identity) when called for multiple indexes
60    * corresponding to prefixes that have the same description.
61    */
62   @Override
getDescription(int index)63   public String getDescription(int index) {
64     int indexInDescriptionPool =
65         readWordFromBuffer(descriptionIndexes, descIndexSizeInBytes, index);
66     return descriptionPool[indexInDescriptionPool];
67   }
68 
69   @Override
readFromSortedMap(SortedMap<Integer, String> areaCodeMap)70   public void readFromSortedMap(SortedMap<Integer, String> areaCodeMap) {
71     SortedSet<String> descriptionsSet = new TreeSet<String>();
72     numOfEntries = areaCodeMap.size();
73     prefixSizeInBytes = getOptimalNumberOfBytesForValue(areaCodeMap.lastKey());
74     phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
75 
76     // Fill the phone number prefixes byte buffer, the set of possible lengths of prefixes and the
77     // description set.
78     int index = 0;
79     for (Entry<Integer, String> entry : areaCodeMap.entrySet()) {
80       int prefix = entry.getKey();
81       storeWordInBuffer(phoneNumberPrefixes, prefixSizeInBytes, index, prefix);
82       possibleLengths.add((int) Math.log10(prefix) + 1);
83       descriptionsSet.add(entry.getValue());
84       ++index;
85     }
86     createDescriptionPool(descriptionsSet, areaCodeMap);
87   }
88 
89   /**
90    * Creates the description pool from the provided set of string descriptions and area code map.
91    */
createDescriptionPool(SortedSet<String> descriptionsSet, SortedMap<Integer, String> areaCodeMap)92   private void createDescriptionPool(SortedSet<String> descriptionsSet,
93       SortedMap<Integer, String> areaCodeMap) {
94     descIndexSizeInBytes = getOptimalNumberOfBytesForValue(descriptionsSet.size() - 1);
95     descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes);
96     descriptionPool = new String[descriptionsSet.size()];
97     descriptionsSet.toArray(descriptionPool);
98 
99     // Map the phone number prefixes to the descriptions.
100     int index = 0;
101     for (int i = 0; i < numOfEntries; i++) {
102       int prefix = readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, i);
103       String description = areaCodeMap.get(prefix);
104       int positionInDescriptionPool = Arrays.binarySearch(descriptionPool, description);
105       storeWordInBuffer(descriptionIndexes, descIndexSizeInBytes, index, positionInDescriptionPool);
106       ++index;
107     }
108   }
109 
110   @Override
readExternal(ObjectInput objectInput)111   public void readExternal(ObjectInput objectInput) throws IOException {
112     // Read binary words sizes.
113     prefixSizeInBytes = objectInput.readInt();
114     descIndexSizeInBytes = objectInput.readInt();
115 
116     // Read possible lengths.
117     int sizeOfLengths = objectInput.readInt();
118     possibleLengths.clear();
119     for (int i = 0; i < sizeOfLengths; i++) {
120       possibleLengths.add(objectInput.readInt());
121     }
122 
123     // Read description pool size.
124     int descriptionPoolSize = objectInput.readInt();
125     // Read description pool.
126     if (descriptionPool == null || descriptionPool.length < descriptionPoolSize) {
127       descriptionPool = new String[descriptionPoolSize];
128     }
129     for (int i = 0; i < descriptionPoolSize; i++) {
130       String description = objectInput.readUTF();
131       descriptionPool[i] = description;
132     }
133     readEntries(objectInput);
134   }
135 
136   /**
137    * Reads the area code entries from the provided input stream and stores them to the internal byte
138    * buffers.
139    */
readEntries(ObjectInput objectInput)140   private void readEntries(ObjectInput objectInput) throws IOException {
141     numOfEntries = objectInput.readInt();
142     if (phoneNumberPrefixes == null || phoneNumberPrefixes.capacity() < numOfEntries) {
143       phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes);
144     }
145     if (descriptionIndexes == null || descriptionIndexes.capacity() < numOfEntries) {
146       descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes);
147     }
148     for (int i = 0; i < numOfEntries; i++) {
149       readExternalWord(objectInput, prefixSizeInBytes, phoneNumberPrefixes, i);
150       readExternalWord(objectInput, descIndexSizeInBytes, descriptionIndexes, i);
151     }
152   }
153 
154   @Override
writeExternal(ObjectOutput objectOutput)155   public void writeExternal(ObjectOutput objectOutput) throws IOException {
156     // Write binary words sizes.
157     objectOutput.writeInt(prefixSizeInBytes);
158     objectOutput.writeInt(descIndexSizeInBytes);
159 
160     // Write possible lengths.
161     int sizeOfLengths = possibleLengths.size();
162     objectOutput.writeInt(sizeOfLengths);
163     for (Integer length : possibleLengths) {
164       objectOutput.writeInt(length);
165     }
166 
167     // Write description pool size.
168     objectOutput.writeInt(descriptionPool.length);
169     // Write description pool.
170     for (String description : descriptionPool) {
171       objectOutput.writeUTF(description);
172     }
173 
174     // Write entries.
175     objectOutput.writeInt(numOfEntries);
176     for (int i = 0; i < numOfEntries; i++) {
177       writeExternalWord(objectOutput, prefixSizeInBytes, phoneNumberPrefixes, i);
178       writeExternalWord(objectOutput, descIndexSizeInBytes, descriptionIndexes, i);
179     }
180   }
181 
182   /**
183    * Gets the minimum number of bytes that can be used to store the provided {@code value}.
184    */
getOptimalNumberOfBytesForValue(int value)185   private static int getOptimalNumberOfBytesForValue(int value) {
186     return value <= Short.MAX_VALUE ? SHORT_NUM_BYTES : INT_NUM_BYTES;
187   }
188 
189   /**
190    * Stores a value which is read from the provided {@code objectInput} to the provided byte {@code
191    * buffer} at the specified {@code index}.
192    *
193    * @param objectInput  the object input stream from which the value is read
194    * @param wordSize  the number of bytes used to store the value read from the stream
195    * @param outputBuffer  the byte buffer to which the value is stored
196    * @param index  the index where the value is stored in the buffer
197    * @throws IOException  if an error occurred reading from the object input stream
198    */
readExternalWord(ObjectInput objectInput, int wordSize, ByteBuffer outputBuffer, int index)199   private static void readExternalWord(ObjectInput objectInput, int wordSize,
200       ByteBuffer outputBuffer, int index) throws IOException {
201     int wordIndex = index * wordSize;
202     if (wordSize == SHORT_NUM_BYTES) {
203       outputBuffer.putShort(wordIndex, objectInput.readShort());
204     } else {
205       outputBuffer.putInt(wordIndex, objectInput.readInt());
206     }
207   }
208 
209   /**
210    * Writes the value read from the provided byte {@code buffer} at the specified {@code index} to
211    * the provided {@code objectOutput}.
212    *
213    * @param objectOutput  the object output stream to which the value is written
214    * @param wordSize  the number of bytes used to store the value
215    * @param inputBuffer  the byte buffer from which the value is read
216    * @param index  the index of the value in the the byte buffer
217    * @throws IOException if an error occurred writing to the provided object output stream
218    */
writeExternalWord(ObjectOutput objectOutput, int wordSize, ByteBuffer inputBuffer, int index)219   private static void writeExternalWord(ObjectOutput objectOutput, int wordSize,
220       ByteBuffer inputBuffer, int index) throws IOException {
221     int wordIndex = index * wordSize;
222     if (wordSize == SHORT_NUM_BYTES) {
223       objectOutput.writeShort(inputBuffer.getShort(wordIndex));
224     } else {
225       objectOutput.writeInt(inputBuffer.getInt(wordIndex));
226     }
227   }
228 
229   /**
230    * Reads the {@code value} at the specified {@code index} from the provided byte {@code buffer}.
231    * Note that only integer and short sizes are supported.
232    *
233    * @param buffer  the byte buffer from which the value is read
234    * @param wordSize  the number of bytes used to store the value
235    * @param index  the index where the value is read from
236    *
237    * @return  the value read from the buffer
238    */
readWordFromBuffer(ByteBuffer buffer, int wordSize, int index)239   private static int readWordFromBuffer(ByteBuffer buffer, int wordSize, int index) {
240     int wordIndex = index * wordSize;
241     return wordSize == SHORT_NUM_BYTES ? buffer.getShort(wordIndex) : buffer.getInt(wordIndex);
242   }
243 
244   /**
245    * Stores the provided {@code value} to the provided byte {@code buffer} at the specified {@code
246    * index} using the provided {@code wordSize} in bytes. Note that only integer and short sizes are
247    * supported.
248    *
249    * @param buffer  the byte buffer to which the value is stored
250    * @param wordSize  the number of bytes used to store the provided value
251    * @param index  the index to which the value is stored
252    * @param value  the value that is stored assuming it does not require more than the specified
253    *    number of bytes.
254    */
storeWordInBuffer(ByteBuffer buffer, int wordSize, int index, int value)255   private static void storeWordInBuffer(ByteBuffer buffer, int wordSize, int index, int value) {
256     int wordIndex = index * wordSize;
257     if (wordSize == SHORT_NUM_BYTES) {
258       buffer.putShort(wordIndex, (short) value);
259     } else {
260       buffer.putInt(wordIndex, value);
261     }
262   }
263 }
264