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