/* * Copyright (C) 2011 The Libphonenumber Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.i18n.phonenumbers.geocoding; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Map.Entry; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeSet; /** * Flyweight area code map storage strategy that uses a table to store unique strings and shorts to * store the prefix and description indexes when possible. It is particularly space-efficient when * the provided area code map contains a lot of redundant descriptions. * * @author Philippe Liard */ final class FlyweightMapStorage extends AreaCodeMapStorageStrategy { // Size of short and integer types in bytes. private static final int SHORT_NUM_BYTES = Short.SIZE / 8; private static final int INT_NUM_BYTES = Integer.SIZE / 8; // The number of bytes used to store a phone number prefix. private int prefixSizeInBytes; // The number of bytes used to store a description index. It is computed from the size of the // description pool containing all the strings. private int descIndexSizeInBytes; private ByteBuffer phoneNumberPrefixes; private ByteBuffer descriptionIndexes; // Sorted string array of unique description strings. private String[] descriptionPool; @Override public int getPrefix(int index) { return readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, index); } /** * This implementation returns the same string (same identity) when called for multiple indexes * corresponding to prefixes that have the same description. */ @Override public String getDescription(int index) { int indexInDescriptionPool = readWordFromBuffer(descriptionIndexes, descIndexSizeInBytes, index); return descriptionPool[indexInDescriptionPool]; } @Override public void readFromSortedMap(SortedMap areaCodeMap) { SortedSet descriptionsSet = new TreeSet(); numOfEntries = areaCodeMap.size(); prefixSizeInBytes = getOptimalNumberOfBytesForValue(areaCodeMap.lastKey()); phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes); // Fill the phone number prefixes byte buffer, the set of possible lengths of prefixes and the // description set. int index = 0; for (Entry entry : areaCodeMap.entrySet()) { int prefix = entry.getKey(); storeWordInBuffer(phoneNumberPrefixes, prefixSizeInBytes, index, prefix); possibleLengths.add((int) Math.log10(prefix) + 1); descriptionsSet.add(entry.getValue()); ++index; } createDescriptionPool(descriptionsSet, areaCodeMap); } /** * Creates the description pool from the provided set of string descriptions and area code map. */ private void createDescriptionPool(SortedSet descriptionsSet, SortedMap areaCodeMap) { descIndexSizeInBytes = getOptimalNumberOfBytesForValue(descriptionsSet.size() - 1); descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes); descriptionPool = new String[descriptionsSet.size()]; descriptionsSet.toArray(descriptionPool); // Map the phone number prefixes to the descriptions. int index = 0; for (int i = 0; i < numOfEntries; i++) { int prefix = readWordFromBuffer(phoneNumberPrefixes, prefixSizeInBytes, i); String description = areaCodeMap.get(prefix); int positionInDescriptionPool = Arrays.binarySearch(descriptionPool, description); storeWordInBuffer(descriptionIndexes, descIndexSizeInBytes, index, positionInDescriptionPool); ++index; } } @Override public void readExternal(ObjectInput objectInput) throws IOException { // Read binary words sizes. prefixSizeInBytes = objectInput.readInt(); descIndexSizeInBytes = objectInput.readInt(); // Read possible lengths. int sizeOfLengths = objectInput.readInt(); possibleLengths.clear(); for (int i = 0; i < sizeOfLengths; i++) { possibleLengths.add(objectInput.readInt()); } // Read description pool size. int descriptionPoolSize = objectInput.readInt(); // Read description pool. if (descriptionPool == null || descriptionPool.length < descriptionPoolSize) { descriptionPool = new String[descriptionPoolSize]; } for (int i = 0; i < descriptionPoolSize; i++) { String description = objectInput.readUTF(); descriptionPool[i] = description; } readEntries(objectInput); } /** * Reads the area code entries from the provided input stream and stores them to the internal byte * buffers. */ private void readEntries(ObjectInput objectInput) throws IOException { numOfEntries = objectInput.readInt(); if (phoneNumberPrefixes == null || phoneNumberPrefixes.capacity() < numOfEntries) { phoneNumberPrefixes = ByteBuffer.allocate(numOfEntries * prefixSizeInBytes); } if (descriptionIndexes == null || descriptionIndexes.capacity() < numOfEntries) { descriptionIndexes = ByteBuffer.allocate(numOfEntries * descIndexSizeInBytes); } for (int i = 0; i < numOfEntries; i++) { readExternalWord(objectInput, prefixSizeInBytes, phoneNumberPrefixes, i); readExternalWord(objectInput, descIndexSizeInBytes, descriptionIndexes, i); } } @Override public void writeExternal(ObjectOutput objectOutput) throws IOException { // Write binary words sizes. objectOutput.writeInt(prefixSizeInBytes); objectOutput.writeInt(descIndexSizeInBytes); // Write possible lengths. int sizeOfLengths = possibleLengths.size(); objectOutput.writeInt(sizeOfLengths); for (Integer length : possibleLengths) { objectOutput.writeInt(length); } // Write description pool size. objectOutput.writeInt(descriptionPool.length); // Write description pool. for (String description : descriptionPool) { objectOutput.writeUTF(description); } // Write entries. objectOutput.writeInt(numOfEntries); for (int i = 0; i < numOfEntries; i++) { writeExternalWord(objectOutput, prefixSizeInBytes, phoneNumberPrefixes, i); writeExternalWord(objectOutput, descIndexSizeInBytes, descriptionIndexes, i); } } /** * Gets the minimum number of bytes that can be used to store the provided {@code value}. */ private static int getOptimalNumberOfBytesForValue(int value) { return value <= Short.MAX_VALUE ? SHORT_NUM_BYTES : INT_NUM_BYTES; } /** * Stores a value which is read from the provided {@code objectInput} to the provided byte {@code * buffer} at the specified {@code index}. * * @param objectInput the object input stream from which the value is read * @param wordSize the number of bytes used to store the value read from the stream * @param outputBuffer the byte buffer to which the value is stored * @param index the index where the value is stored in the buffer * @throws IOException if an error occurred reading from the object input stream */ private static void readExternalWord(ObjectInput objectInput, int wordSize, ByteBuffer outputBuffer, int index) throws IOException { int wordIndex = index * wordSize; if (wordSize == SHORT_NUM_BYTES) { outputBuffer.putShort(wordIndex, objectInput.readShort()); } else { outputBuffer.putInt(wordIndex, objectInput.readInt()); } } /** * Writes the value read from the provided byte {@code buffer} at the specified {@code index} to * the provided {@code objectOutput}. * * @param objectOutput the object output stream to which the value is written * @param wordSize the number of bytes used to store the value * @param inputBuffer the byte buffer from which the value is read * @param index the index of the value in the the byte buffer * @throws IOException if an error occurred writing to the provided object output stream */ private static void writeExternalWord(ObjectOutput objectOutput, int wordSize, ByteBuffer inputBuffer, int index) throws IOException { int wordIndex = index * wordSize; if (wordSize == SHORT_NUM_BYTES) { objectOutput.writeShort(inputBuffer.getShort(wordIndex)); } else { objectOutput.writeInt(inputBuffer.getInt(wordIndex)); } } /** * Reads the {@code value} at the specified {@code index} from the provided byte {@code buffer}. * Note that only integer and short sizes are supported. * * @param buffer the byte buffer from which the value is read * @param wordSize the number of bytes used to store the value * @param index the index where the value is read from * * @return the value read from the buffer */ private static int readWordFromBuffer(ByteBuffer buffer, int wordSize, int index) { int wordIndex = index * wordSize; return wordSize == SHORT_NUM_BYTES ? buffer.getShort(wordIndex) : buffer.getInt(wordIndex); } /** * Stores the provided {@code value} to the provided byte {@code buffer} at the specified {@code * index} using the provided {@code wordSize} in bytes. Note that only integer and short sizes are * supported. * * @param buffer the byte buffer to which the value is stored * @param wordSize the number of bytes used to store the provided value * @param index the index to which the value is stored * @param value the value that is stored assuming it does not require more than the specified * number of bytes. */ private static void storeWordInBuffer(ByteBuffer buffer, int wordSize, int index, int value) { int wordIndex = index * wordSize; if (wordSize == SHORT_NUM_BYTES) { buffer.putShort(wordIndex, (short) value); } else { buffer.putInt(wordIndex, value); } } }