• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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