Home | History | Annotate | Download | only in prefixmapper
      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.prefixmapper;
     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 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  */
     36 final 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