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
17package com.android.i18n.phonenumbers.prefixmapper;
18
19import java.io.IOException;
20import java.io.ObjectInput;
21import java.io.ObjectOutput;
22import java.nio.ByteBuffer;
23import java.util.Arrays;
24import java.util.Map.Entry;
25import java.util.SortedMap;
26import java.util.SortedSet;
27import java.util.TreeSet;
28
29/**
30 * Flyweight phone prefix map storage strategy that uses a table to store unique strings and shorts
31 * to store the prefix and description indexes when possible. It is particularly space-efficient
32 * when the provided phone prefix map contains a lot of redundant descriptions.
33 *
34 * @author Philippe Liard
35 */
36final class FlyweightMapStorage extends PhonePrefixMapStorageStrategy {
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
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
63  public String getDescription(int index) {
64    int indexInDescriptionPool =
65        readWordFromBuffer(descriptionIndexes, descIndexSizeInBytes, index);
66    return descriptionPool[indexInDescriptionPool];
67  }
68
69  @Override
70  public void readFromSortedMap(SortedMap<Integer, String> phonePrefixMap) {
71    SortedSet<String> descriptionsSet = new TreeSet<String>();
72    numOfEntries = phonePrefixMap.size();
73    prefixSizeInBytes = getOptimalNumberOfBytesForValue(phonePrefixMap.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 : phonePrefixMap.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, phonePrefixMap);
87  }
88
89  /**
90   * Creates the description pool from the provided set of string descriptions and phone prefix map.
91   */
92  private void createDescriptionPool(SortedSet<String> descriptionsSet,
93      SortedMap<Integer, String> phonePrefixMap) {
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 = phonePrefixMap.get(prefix);
104      int positionInDescriptionPool = Arrays.binarySearch(descriptionPool, description);
105      storeWordInBuffer(descriptionIndexes, descIndexSizeInBytes, index, positionInDescriptionPool);
106      ++index;
107    }
108  }
109
110  @Override
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 phone prefix entries from the provided input stream and stores them to the internal
138   * byte buffers.
139   */
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
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   */
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   */
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   */
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   */
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   */
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