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 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> 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 */ 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 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 */ 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