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