gt:     int wordIndex = index * wordSize;
context: /*
   * 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<Integer, String> areaCodeMap) {
      SortedSet<String> descriptionsSet = new TreeSet<String>();
      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<Integer, String> 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<String> descriptionsSet,
        SortedMap<Integer, String> 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 {
  