1 /*
2  * Copyright (C) 2006 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 android.util;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 
22 import com.android.internal.util.ArrayUtils;
23 import com.android.internal.util.GrowingArrayUtils;
24 
25 import libcore.util.EmptyArray;
26 
27 /**
28  * SparseBooleanArrays map integers to booleans.
29  * Unlike a normal array of booleans
30  * there can be gaps in the indices.  It is intended to be more memory efficient
31  * than using a HashMap to map Integers to Booleans, both because it avoids
32  * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
33  * for each mapping.
34  *
35  * <p>Note that this container keeps its mappings in an array data structure,
36  * using a binary search to find keys.  The implementation is not intended to be appropriate for
37  * data structures
38  * that may contain large numbers of items.  It is generally slower than a traditional
39  * HashMap, since lookups require a binary search and adds and removes require inserting
40  * and deleting entries in the array.  For containers holding up to hundreds of items,
41  * the performance difference is not significant, less than 50%.</p>
42  *
43  * <p>It is possible to iterate over the items in this container using
44  * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
45  * <code>keyAt(int)</code> with ascending values of the index will return the
46  * keys in ascending order, or the values corresponding to the keys in ascending
47  * order in the case of <code>valueAt(int)</code>.</p>
48  */
49 public class SparseBooleanArray implements Cloneable {
50     /**
51      * Creates a new SparseBooleanArray containing no mappings.
52      */
SparseBooleanArray()53     public SparseBooleanArray() {
54         this(0);
55     }
56 
57     /**
58      * Creates a new SparseBooleanArray containing no mappings that will not
59      * require any additional memory allocation to store the specified
60      * number of mappings.  If you supply an initial capacity of 0, the
61      * sparse array will be initialized with a light-weight representation
62      * not requiring any additional array allocations.
63      */
SparseBooleanArray(int initialCapacity)64     public SparseBooleanArray(int initialCapacity) {
65         if (initialCapacity == 0) {
66             mKeys = EmptyArray.INT;
67             mValues = EmptyArray.BOOLEAN;
68         } else {
69             mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
70             mValues = new boolean[mKeys.length];
71         }
72         mSize = 0;
73     }
74 
75     @Override
clone()76     public SparseBooleanArray clone() {
77         SparseBooleanArray clone = null;
78         try {
79             clone = (SparseBooleanArray) super.clone();
80             clone.mKeys = mKeys.clone();
81             clone.mValues = mValues.clone();
82         } catch (CloneNotSupportedException cnse) {
83             /* ignore */
84         }
85         return clone;
86     }
87 
88     /**
89      * Gets the boolean mapped from the specified key, or <code>false</code>
90      * if no such mapping has been made.
91      */
get(int key)92     public boolean get(int key) {
93         return get(key, false);
94     }
95 
96     /**
97      * Gets the boolean mapped from the specified key, or the specified value
98      * if no such mapping has been made.
99      */
get(int key, boolean valueIfKeyNotFound)100     public boolean get(int key, boolean valueIfKeyNotFound) {
101         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
102 
103         if (i < 0) {
104             return valueIfKeyNotFound;
105         } else {
106             return mValues[i];
107         }
108     }
109 
110     /**
111      * Removes the mapping from the specified key, if there was any.
112      */
delete(int key)113     public void delete(int key) {
114         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
115 
116         if (i >= 0) {
117             System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
118             System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1));
119             mSize--;
120         }
121     }
122 
123     /**
124      * Removes the mapping at the specified index.
125      * <p>
126      * For indices outside of the range {@code 0...size()-1}, the behavior is undefined.
127      */
removeAt(int index)128     public void removeAt(int index) {
129         System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
130         System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
131         mSize--;
132     }
133 
134     /**
135      * Adds a mapping from the specified key to the specified value,
136      * replacing the previous mapping from the specified key if there
137      * was one.
138      */
put(int key, boolean value)139     public void put(int key, boolean value) {
140         int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
141 
142         if (i >= 0) {
143             mValues[i] = value;
144         } else {
145             i = ~i;
146 
147             mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
148             mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
149             mSize++;
150         }
151     }
152 
153     /**
154      * Returns the number of key-value mappings that this SparseBooleanArray
155      * currently stores.
156      */
size()157     public int size() {
158         return mSize;
159     }
160 
161     /**
162      * Given an index in the range <code>0...size()-1</code>, returns
163      * the key from the <code>index</code>th key-value mapping that this
164      * SparseBooleanArray stores.
165      *
166      * <p>The keys corresponding to indices in ascending order are guaranteed to
167      * be in ascending order, e.g., <code>keyAt(0)</code> will return the
168      * smallest key and <code>keyAt(size()-1)</code> will return the largest
169      * key.</p>
170      *
171      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
172      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
173      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
174      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
175      */
keyAt(int index)176     public int keyAt(int index) {
177         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
178             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
179             // Check if exception should be thrown outside of the critical path.
180             throw new ArrayIndexOutOfBoundsException(index);
181         }
182         return mKeys[index];
183     }
184 
185     /**
186      * Given an index in the range <code>0...size()-1</code>, returns
187      * the value from the <code>index</code>th key-value mapping that this
188      * SparseBooleanArray stores.
189      *
190      * <p>The values corresponding to indices in ascending order are guaranteed
191      * to be associated with keys in ascending order, e.g.,
192      * <code>valueAt(0)</code> will return the value associated with the
193      * smallest key and <code>valueAt(size()-1)</code> will return the value
194      * associated with the largest key.</p>
195      *
196      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
197      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
198      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
199      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
200      */
valueAt(int index)201     public boolean valueAt(int index) {
202         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
203             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
204             // Check if exception should be thrown outside of the critical path.
205             throw new ArrayIndexOutOfBoundsException(index);
206         }
207         return mValues[index];
208     }
209 
210     /**
211      * Directly set the value at a particular index.
212      *
213      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
214      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
215      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
216      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
217      */
setValueAt(int index, boolean value)218     public void setValueAt(int index, boolean value) {
219         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
220             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
221             // Check if exception should be thrown outside of the critical path.
222             throw new ArrayIndexOutOfBoundsException(index);
223         }
224         mValues[index] = value;
225     }
226 
227     /** @hide */
setKeyAt(int index, int key)228     public void setKeyAt(int index, int key) {
229         if (index >= mSize) {
230             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
231             throw new ArrayIndexOutOfBoundsException(index);
232         }
233         mKeys[index] = key;
234     }
235 
236     /**
237      * Returns the index for which {@link #keyAt} would return the
238      * specified key, or a negative number if the specified
239      * key is not mapped.
240      */
indexOfKey(int key)241     public int indexOfKey(int key) {
242         return ContainerHelpers.binarySearch(mKeys, mSize, key);
243     }
244 
245     /**
246      * Returns an index for which {@link #valueAt} would return the
247      * specified key, or a negative number if no keys map to the
248      * specified value.
249      * Beware that this is a linear search, unlike lookups by key,
250      * and that multiple keys can map to the same value and this will
251      * find only one of them.
252      */
indexOfValue(boolean value)253     public int indexOfValue(boolean value) {
254         for (int i = 0; i < mSize; i++)
255             if (mValues[i] == value)
256                 return i;
257 
258         return -1;
259     }
260 
261     /**
262      * Removes all key-value mappings from this SparseBooleanArray.
263      */
clear()264     public void clear() {
265         mSize = 0;
266     }
267 
268     /**
269      * Puts a key/value pair into the array, optimizing for the case where
270      * the key is greater than all existing keys in the array.
271      */
append(int key, boolean value)272     public void append(int key, boolean value) {
273         if (mSize != 0 && key <= mKeys[mSize - 1]) {
274             put(key, value);
275             return;
276         }
277 
278         mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
279         mValues = GrowingArrayUtils.append(mValues, mSize, value);
280         mSize++;
281     }
282 
283     @Override
hashCode()284     public int hashCode() {
285         int hashCode = mSize;
286         for (int i = 0; i < mSize; i++) {
287             hashCode = 31 * hashCode + mKeys[i] | (mValues[i] ? 1 : 0);
288         }
289         return hashCode;
290     }
291 
292     @Override
equals(@ullable Object that)293     public boolean equals(@Nullable Object that) {
294       if (this == that) {
295           return true;
296       }
297 
298       if (!(that instanceof SparseBooleanArray)) {
299           return false;
300       }
301 
302       SparseBooleanArray other = (SparseBooleanArray) that;
303       if (mSize != other.mSize) {
304           return false;
305       }
306 
307       for (int i = 0; i < mSize; i++) {
308           if (mKeys[i] != other.mKeys[i]) {
309               return false;
310           }
311           if (mValues[i] != other.mValues[i]) {
312               return false;
313           }
314       }
315       return true;
316     }
317 
318     /**
319      * {@inheritDoc}
320      *
321      * <p>This implementation composes a string by iterating over its mappings.
322      */
323     @Override
toString()324     public String toString() {
325         if (size() <= 0) {
326             return "{}";
327         }
328 
329         StringBuilder buffer = new StringBuilder(mSize * 28);
330         buffer.append('{');
331         for (int i=0; i<mSize; i++) {
332             if (i > 0) {
333                 buffer.append(", ");
334             }
335             int key = keyAt(i);
336             buffer.append(key);
337             buffer.append('=');
338             boolean value = valueAt(i);
339             buffer.append(value);
340         }
341         buffer.append('}');
342         return buffer.toString();
343     }
344 
345     @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
346     private int[] mKeys;
347     @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, boolean)
348     private boolean[] mValues;
349     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
350     private int mSize;
351 }
352