1 /* 2 * Copyright (C) 2020 The Android Open Source Project 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.timezone.location.storage.table.packed.read; 18 19 import com.android.timezone.location.storage.block.read.BlockData; 20 import com.android.timezone.location.storage.table.reader.IntValueTable.IntValueEntryMatcher; 21 import com.android.timezone.location.storage.table.reader.LongValueTable.LongValueEntryMatcher; 22 import com.android.timezone.location.storage.util.BitwiseUtils; 23 24 import java.util.Objects; 25 26 /** 27 * A helper class that knows how to read packed tables from {@link BlockData}. Packed tables are a 28 * sorted structure containing fixed-width records made up of a key of N bits and a value of the 29 * remaining bits. Keys do not have to be unique. The combination of ordering by key and fixed-width 30 * records makes packed tables quick to search by key and also compact. 31 * 32 * <p>The information about the entry size / key size / how the value is to be treated is encoded in 33 * the first few bytes of the block data along with the shared table data. 34 * 35 * <p>The implementation is limited in that it can only read entries up to 8 bytes wide. Keys can be 36 * (at most) signed 31-bit values. Entries can only be multiples of a byte. 37 */ 38 public final class PackedTableReader { 39 40 /** The bit in the bit field used to store whether the value is to be treated as signed. */ 41 public static final int BIT_FIELD_SIGNED_VALUE_MASK = 0x01; 42 43 /** The block data to read from . */ 44 private final BlockData mBlockData; 45 46 /** The number of bytes in the BlockData used to hold table / entry metadata. */ 47 private final int mHeaderLengthBytes; 48 49 /** The number of bytes per entry. */ 50 private final int mEntrySizeBytes; 51 52 private final int mEntrySizeBits; 53 54 private final int mKeySizeBits; 55 56 private final int mValueSizeBits; 57 58 private final int mEntryCount; 59 60 /** Domain-specific data that should be common to all entries. */ 61 private final byte[] mSharedData; 62 63 /** 64 * True if the value is to be treated as a signed value, i.e. whether its sign should be 65 * extended when converting it to a Java type. 66 */ 67 private final boolean mSignedValue; 68 69 /** 70 * True if the value can fit in a signed / unsigned int (depending on {@link #mSignedValue} 71 * without risk of overflow. 72 */ 73 private final boolean mIntValueSupported; 74 PackedTableReader(BlockData blockData)75 public PackedTableReader(BlockData blockData) { 76 mBlockData = Objects.requireNonNull(blockData); 77 78 int offset = 0; 79 80 mSharedData = blockData.getTinyByteArray(offset); 81 offset += Byte.BYTES + mSharedData.length; 82 83 // Boolean properties are extracted from a 32-bit bit field. 84 int bitField = blockData.getUnsignedByte(offset); 85 offset += 1; 86 mSignedValue = (bitField & BIT_FIELD_SIGNED_VALUE_MASK) > 0; 87 88 mEntrySizeBytes = blockData.getUnsignedByte(offset); 89 offset += 1; 90 if (mEntrySizeBytes > 8) { 91 throw new IllegalStateException( 92 "Entries cannot exceed 8 bytes in width, entrySizeBytes=" + mEntrySizeBytes); 93 } 94 mEntrySizeBits = mEntrySizeBytes * 8; 95 96 mKeySizeBits = blockData.getUnsignedByte(offset); 97 offset += 1; 98 99 // There must be at least one key bit and at least one value bit. The key cannot exceed 31 100 // bits in size. 101 int maxKeySizeBits = Math.min(31, mEntrySizeBits - 1); 102 if (mKeySizeBits < 1 || mKeySizeBits > maxKeySizeBits) { 103 throw new IllegalStateException("keySizeBits must be >= 1 and <= " + maxKeySizeBits); 104 } 105 106 // The value bits are whatever entry bits are not key bits. 107 mValueSizeBits = mEntrySizeBits - mKeySizeBits; 108 if (mSignedValue) { 109 mIntValueSupported = mValueSizeBits <= Integer.SIZE; 110 } else { 111 mIntValueSupported = mValueSizeBits <= Integer.SIZE - 1; 112 } 113 114 mHeaderLengthBytes = offset; 115 116 // Calculate the entry count. Note: There can be more than 2^N entries because duplicate 117 // keys are supported. 118 mEntryCount = (blockData.getSize() - mHeaderLengthBytes) / mEntrySizeBytes; 119 } 120 121 /** Returns the number of bytes used to store each entry. */ getEntrySizeBytes()122 public int getEntrySizeBytes() { 123 return mEntrySizeBytes; 124 } 125 126 /** Returns the number of bits devoted to the key. */ getKeySizeBits()127 public int getKeySizeBits() { 128 return mKeySizeBits; 129 } 130 131 /** Returns true if the value is signed. */ isValueSigned()132 public boolean isValueSigned() { 133 return mSignedValue; 134 } 135 136 /** Returns the number of bits devoted to the value. */ getValueSizeBits()137 public int getValueSizeBits() { 138 return mValueSizeBits; 139 } 140 141 /** Returns the table's shared data. */ getSharedData()142 public byte[] getSharedData() { 143 return mSharedData; 144 } 145 146 /** Returns the number of entries in the table. */ getEntryCount()147 public int getEntryCount() { 148 return mEntryCount; 149 } 150 151 /** 152 * Returns an {@link Entry} with the specified key or {@code null} if there isn't one. If 153 * multiple entries have the key, then an arbitrary entry with the key is returned. 154 */ getEntry(int key)155 public Entry getEntry(int key) { 156 BitwiseUtils.checkUnsignedValueInRange(mKeySizeBits, key); 157 return findLongValueEntry((k, v) -> key - k); 158 } 159 160 /** Returns the {@link Entry} with the specified index. */ getEntryByIndex(int i)161 public Entry getEntryByIndex(int i) { 162 long entryBytes = getEntryBytesForIndex(i); 163 return new Entry(i, entryBytes); 164 } 165 166 /** 167 * Finds an entry in the table using the supplied matcher, return {@code null} if none match. If 168 * multiple entries in the table match then an arbitrary matching entry is returned. 169 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 170 * of overflow with an int. 171 */ findIntValueEntry(IntValueEntryMatcher entryMatcher)172 public Entry findIntValueEntry(IntValueEntryMatcher entryMatcher) { 173 checkIntValueSupported(); 174 return findLongValueEntry(convertToLongValueEntryMatcher(entryMatcher)); 175 } 176 177 /** 178 * Finds an entry in the table using the supplied matcher, return {@code null} if none match. If 179 * multiple entries in the table match then an arbitrary matching entry is returned. 180 */ findLongValueEntry(LongValueEntryMatcher entryMatcher)181 public Entry findLongValueEntry(LongValueEntryMatcher entryMatcher) { 182 int lower = 0; 183 int upper = mEntryCount - 1; 184 return binarySearch(lower, upper, entryMatcher); 185 } 186 binarySearch(int lower, int upper, LongValueEntryMatcher entryMatcher)187 private Entry binarySearch(int lower, int upper, LongValueEntryMatcher entryMatcher) { 188 while (lower <= upper) { 189 int mid = (lower + upper) / 2; 190 191 long entryBytes = getEntryBytesForIndex(mid); 192 int entryKey = getKeyFromEntryBytes(entryBytes); 193 long entryValue = getLongValueFromEntryBytes(entryBytes); 194 int compareResult = entryMatcher.compare(entryKey, entryValue); 195 if (compareResult > 0) { 196 lower = mid + 1; 197 } else if (compareResult < 0) { 198 upper = mid - 1; 199 } else { 200 return new Entry(mid, entryBytes); 201 } 202 } 203 return null; 204 } 205 getEntryBytesForIndex(int index)206 private long getEntryBytesForIndex(int index) { 207 if (index < 0 || index >= mEntryCount) { 208 throw new IndexOutOfBoundsException(); 209 } 210 int byteOffset = mHeaderLengthBytes + (index * mEntrySizeBytes); 211 return mBlockData.getValueAsLong(mEntrySizeBytes, byteOffset, mSignedValue); 212 } 213 getKeyFromEntryBytes(long entryBytes)214 private int getKeyFromEntryBytes(long entryBytes) { 215 return getKeyFromEntryBytes(mEntrySizeBits, mKeySizeBits, entryBytes); 216 } 217 218 /** 219 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 220 * of overflow with an int. 221 */ getIntValueFromEntryBytes(long entryBytes)222 private int getIntValueFromEntryBytes(long entryBytes) { 223 checkIntValueSupported(); 224 long value = getLongValueFromEntryBytes(entryBytes); 225 return (int) value; 226 } 227 getLongValueFromEntryBytes(long entryBytes)228 private long getLongValueFromEntryBytes(long entryBytes) { 229 long value = BitwiseUtils.extractLowBits(mValueSizeBits, entryBytes); 230 if (mSignedValue) { 231 value = BitwiseUtils.signExtendToLong(mValueSizeBits, value); 232 } 233 return value; 234 } 235 checkIntValueSupported()236 private void checkIntValueSupported() { 237 if (!mIntValueSupported) { 238 throw new IllegalStateException("value size is too big to support int," 239 + " mSignedValue=" + mSignedValue + ", mValueSizeBits=" + mValueSizeBits); 240 } 241 } 242 243 /** 244 * An entry from a packed table. 245 */ 246 public final class Entry { 247 248 final int mIndex; 249 250 /** The entry bytes held in a long. */ 251 private final long mEntryBytes; 252 253 /** The key, lazily initialized. Use {@link #getKey()} to access the key. */ 254 private int mKey = -1; 255 Entry(int index, long entryBytes)256 Entry(int index, long entryBytes) { 257 mIndex = index; 258 mEntryBytes = entryBytes; 259 } 260 261 /** Returns the next entry in the table, {@code null} if there isn't one. */ getNext()262 public Entry getNext() { 263 if (mIndex >= mEntryCount - 1) { 264 return null; 265 } 266 int nextIndex = mIndex + 1; 267 return getEntryByIndex(nextIndex); 268 } 269 270 /** Returns the previous entry in the table, {@code null} if there isn't one. */ getPrevious()271 public Entry getPrevious() { 272 if (mIndex == 0) { 273 return null; 274 } 275 int previousIndex = mIndex - 1; 276 return getEntryByIndex(previousIndex); 277 } 278 279 /** 280 * Finds an entry using the supplied matcher via a binary search. Returns {@code null} if no 281 * entry matches. Uses this entry as a starting point. 282 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 283 * of overflow with an int. 284 */ findNearbyEntry(IntValueEntryMatcher entryMatcher)285 public Entry findNearbyEntry(IntValueEntryMatcher entryMatcher) { 286 checkIntValueSupported(); 287 return findNearbyEntry(convertToLongValueEntryMatcher(entryMatcher)); 288 } 289 290 /** 291 * Finds an entry using the supplied matcher via a binary search. Returns {@code null} if no 292 * entry matches. Uses this entry as a starting point. 293 */ findNearbyEntry(LongValueEntryMatcher entryMatcher)294 public Entry findNearbyEntry(LongValueEntryMatcher entryMatcher) { 295 int key = getKey(); 296 int compare = entryMatcher.compare(key, getLongValue()); 297 if (compare == 0) { 298 return this; 299 } 300 301 int lower, upper; 302 if (compare < 0) { 303 lower = 0; 304 upper = mIndex; 305 } else { 306 lower = mIndex; 307 upper = mEntryCount - 1; 308 } 309 return binarySearch(lower, upper, entryMatcher); 310 } 311 312 /** Returns the key for this entry. */ getKey()313 public int getKey() { 314 if (mKey == -1) { 315 mKey = getKeyFromEntryBytes(mEntryBytes); 316 } 317 return mKey; 318 } 319 320 /** Returns the index for this entry. */ getIndex()321 public int getIndex() { 322 return mIndex; 323 } 324 325 /** 326 * Returns the entry's value as an int. 327 * Throws an {@link IllegalStateException} if the table's settings mean that there is a risk 328 * of overflow with an int. 329 */ getIntValue()330 public int getIntValue() { 331 return getIntValueFromEntryBytes(mEntryBytes); 332 } 333 334 /** 335 * Returns the entry's value as a long. 336 */ getLongValue()337 public long getLongValue() { 338 return getLongValueFromEntryBytes(mEntryBytes); 339 } 340 341 @Override equals(Object o)342 public boolean equals(Object o) { 343 if (this == o) { 344 return true; 345 } 346 if (o == null || getClass() != o.getClass()) { 347 return false; 348 } 349 Entry entry = (Entry) o; 350 return mIndex == entry.mIndex 351 && mEntryBytes == entry.mEntryBytes; 352 } 353 354 @Override hashCode()355 public int hashCode() { 356 return Objects.hash(mIndex); 357 } 358 } 359 getKeyFromEntryBytes(int entrySizeBits, int keySizeBits, long entryBytes)360 private static int getKeyFromEntryBytes(int entrySizeBits, int keySizeBits, long entryBytes) { 361 long mask = BitwiseUtils.getMidBitsMask(entrySizeBits, keySizeBits); 362 363 // Extract the key bits and shift them right. 364 long keyBits = mask & entryBytes; 365 int valueSizeBits = entrySizeBits - keySizeBits; 366 return (int) (keyBits >>> valueSizeBits); 367 } 368 convertToLongValueEntryMatcher( IntValueEntryMatcher entryMatcher)369 private static LongValueEntryMatcher convertToLongValueEntryMatcher( 370 IntValueEntryMatcher entryMatcher) { 371 return (key, value) -> entryMatcher.compare(key, (int) value); 372 } 373 } 374