• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
24 import libcore.util.EmptyArray;
25 
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.ConcurrentModificationException;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.function.BiConsumer;
32 import java.util.function.BiFunction;
33 
34 /**
35  * ArrayMap is a generic key->value mapping data structure that is
36  * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
37  * It keeps its mappings in an array data structure -- an integer array of hash
38  * codes for each item, and an Object array of the key/value pairs.  This allows it to
39  * avoid having to create an extra object for every entry put in to the map, and it
40  * also tries to control the growth of the size of these arrays more aggressively
41  * (since growing them only requires copying the entries in the array, not rebuilding
42  * a hash map).
43  *
44  * <p>Note that this implementation is not intended to be appropriate for data structures
45  * that may contain large numbers of items.  It is generally slower than a traditional
46  * HashMap, since lookups require a binary search and adds and removes require inserting
47  * and deleting entries in the array.  For containers holding up to hundreds of items,
48  * the performance difference is not significant, less than 50%.</p>
49  *
50  * <p>Because this container is intended to better balance memory use, unlike most other
51  * standard Java containers it will shrink its array as items are removed from it.  Currently
52  * you have no control over this shrinking -- if you set a capacity and then remove an
53  * item, it may reduce the capacity to better match the current size.  In the future an
54  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
55  *
56  * <p>This structure is <b>NOT</b> thread-safe.</p>
57  */
58 public final class ArrayMap<K, V> implements Map<K, V> {
59     private static final boolean DEBUG = false;
60     private static final String TAG = "ArrayMap";
61 
62     /**
63      * Attempt to spot concurrent modifications to this data structure.
64      *
65      * It's best-effort, but any time we can throw something more diagnostic than an
66      * ArrayIndexOutOfBoundsException deep in the ArrayMap internals it's going to
67      * save a lot of development time.
68      *
69      * Good times to look for CME include after any allocArrays() call and at the end of
70      * functions that change mSize (put/remove/clear).
71      */
72     private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true;
73 
74     /**
75      * The minimum amount by which the capacity of a ArrayMap will increase.
76      * This is tuned to be relatively space-efficient.
77      */
78     private static final int BASE_SIZE = 4;
79 
80     /**
81      * Maximum number of entries to have in array caches.
82      */
83     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
84     private static final int CACHE_SIZE = 10;
85 
86     /**
87      * Special hash array value that indicates the container is immutable.
88      */
89     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
90     static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
91 
92     /**
93      * @hide Special immutable empty ArrayMap.
94      */
95     @UnsupportedAppUsage(maxTargetSdk = 28) // Use your own singleton empty map.
96     public static final ArrayMap EMPTY = new ArrayMap<>(-1);
97 
98     /**
99      * Caches of small array objects to avoid spamming garbage.  The cache
100      * Object[] variable is a pointer to a linked list of array objects.
101      * The first entry in the array is a pointer to the next array in the
102      * list; the second entry is a pointer to the int[] hash code array for it.
103      */
104     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
105     static Object[] mBaseCache;
106     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
107     static int mBaseCacheSize;
108     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
109     static Object[] mTwiceBaseCache;
110     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
111     static int mTwiceBaseCacheSize;
112     /**
113      * Separate locks for each cache since each can be accessed independently of the other without
114      * risk of a deadlock.
115      */
116     private static final Object sBaseCacheLock = new Object();
117     private static final Object sTwiceBaseCacheLock = new Object();
118 
119     private final boolean mIdentityHashCode;
120     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
121     int[] mHashes;
122     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
123     Object[] mArray;
124     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
125     int mSize;
126     private MapCollections<K, V> mCollections;
127 
binarySearchHashes(int[] hashes, int N, int hash)128     private static int binarySearchHashes(int[] hashes, int N, int hash) {
129         try {
130             return ContainerHelpers.binarySearch(hashes, N, hash);
131         } catch (ArrayIndexOutOfBoundsException e) {
132             if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
133                 throw new ConcurrentModificationException();
134             } else {
135                 throw e; // the cache is poisoned at this point, there's not much we can do
136             }
137         }
138     }
139 
140     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
indexOf(Object key, int hash)141     int indexOf(Object key, int hash) {
142         final int N = mSize;
143 
144         // Important fast case: if nothing is in here, nothing to look for.
145         if (N == 0) {
146             return ~0;
147         }
148 
149         int index = binarySearchHashes(mHashes, N, hash);
150 
151         // If the hash code wasn't found, then we have no entry for this key.
152         if (index < 0) {
153             return index;
154         }
155 
156         // If the key at the returned index matches, that's what we want.
157         if (key.equals(mArray[index<<1])) {
158             return index;
159         }
160 
161         // Search for a matching key after the index.
162         int end;
163         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
164             if (key.equals(mArray[end << 1])) return end;
165         }
166 
167         // Search for a matching key before the index.
168         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
169             if (key.equals(mArray[i << 1])) return i;
170         }
171 
172         // Key not found -- return negative value indicating where a
173         // new entry for this key should go.  We use the end of the
174         // hash chain to reduce the number of array entries that will
175         // need to be copied when inserting.
176         return ~end;
177     }
178 
179     @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
indexOfNull()180     int indexOfNull() {
181         final int N = mSize;
182 
183         // Important fast case: if nothing is in here, nothing to look for.
184         if (N == 0) {
185             return ~0;
186         }
187 
188         int index = binarySearchHashes(mHashes, N, 0);
189 
190         // If the hash code wasn't found, then we have no entry for this key.
191         if (index < 0) {
192             return index;
193         }
194 
195         // If the key at the returned index matches, that's what we want.
196         if (null == mArray[index<<1]) {
197             return index;
198         }
199 
200         // Search for a matching key after the index.
201         int end;
202         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
203             if (null == mArray[end << 1]) return end;
204         }
205 
206         // Search for a matching key before the index.
207         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
208             if (null == mArray[i << 1]) return i;
209         }
210 
211         // Key not found -- return negative value indicating where a
212         // new entry for this key should go.  We use the end of the
213         // hash chain to reduce the number of array entries that will
214         // need to be copied when inserting.
215         return ~end;
216     }
217 
218     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
allocArrays(final int size)219     private void allocArrays(final int size) {
220         if (mHashes == EMPTY_IMMUTABLE_INTS) {
221             throw new UnsupportedOperationException("ArrayMap is immutable");
222         }
223         if (size == (BASE_SIZE*2)) {
224             synchronized (sTwiceBaseCacheLock) {
225                 if (mTwiceBaseCache != null) {
226                     final Object[] array = mTwiceBaseCache;
227                     mArray = array;
228                     try {
229                         mTwiceBaseCache = (Object[]) array[0];
230                         mHashes = (int[]) array[1];
231                         if (mHashes != null) {
232                             array[0] = array[1] = null;
233                             mTwiceBaseCacheSize--;
234                             if (DEBUG) {
235                                 Log.d(TAG, "Retrieving 2x cache " + Arrays.toString(mHashes)
236                                         + " now have " + mTwiceBaseCacheSize + " entries");
237                             }
238                             return;
239                         }
240                     } catch (ClassCastException e) {
241                     }
242                     // Whoops!  Someone trampled the array (probably due to not protecting
243                     // their access with a lock).  Our cache is corrupt; report and give up.
244                     Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
245                             + " [1]=" + array[1]);
246                     mTwiceBaseCache = null;
247                     mTwiceBaseCacheSize = 0;
248                 }
249             }
250         } else if (size == BASE_SIZE) {
251             synchronized (sBaseCacheLock) {
252                 if (mBaseCache != null) {
253                     final Object[] array = mBaseCache;
254                     mArray = array;
255                     try {
256                         mBaseCache = (Object[]) array[0];
257                         mHashes = (int[]) array[1];
258                         if (mHashes != null) {
259                             array[0] = array[1] = null;
260                             mBaseCacheSize--;
261                             if (DEBUG) {
262                                 Log.d(TAG, "Retrieving 1x cache " + Arrays.toString(mHashes)
263                                         + " now have " + mBaseCacheSize + " entries");
264                             }
265                             return;
266                         }
267                     } catch (ClassCastException e) {
268                     }
269                     // Whoops!  Someone trampled the array (probably due to not protecting
270                     // their access with a lock).  Our cache is corrupt; report and give up.
271                     Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
272                             + " [1]=" + array[1]);
273                     mBaseCache = null;
274                     mBaseCacheSize = 0;
275                 }
276             }
277         }
278 
279         mHashes = new int[size];
280         mArray = new Object[size<<1];
281     }
282 
283     /**
284      * Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
285      * words, don't pass mHashes or mArray in directly.
286      */
287     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
freeArrays(final int[] hashes, final Object[] array, final int size)288     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
289         if (hashes.length == (BASE_SIZE*2)) {
290             synchronized (sTwiceBaseCacheLock) {
291                 if (mTwiceBaseCacheSize < CACHE_SIZE) {
292                     array[0] = mTwiceBaseCache;
293                     array[1] = hashes;
294                     for (int i=(size<<1)-1; i>=2; i--) {
295                         array[i] = null;
296                     }
297                     mTwiceBaseCache = array;
298                     mTwiceBaseCacheSize++;
299                     if (DEBUG) {
300                         Log.d(TAG, "Storing 2x cache " + Arrays.toString(array)
301                                 + " now have " + mTwiceBaseCacheSize + " entries");
302                     }
303                 }
304             }
305         } else if (hashes.length == BASE_SIZE) {
306             synchronized (sBaseCacheLock) {
307                 if (mBaseCacheSize < CACHE_SIZE) {
308                     array[0] = mBaseCache;
309                     array[1] = hashes;
310                     for (int i=(size<<1)-1; i>=2; i--) {
311                         array[i] = null;
312                     }
313                     mBaseCache = array;
314                     mBaseCacheSize++;
315                     if (DEBUG) {
316                         Log.d(TAG, "Storing 1x cache " + Arrays.toString(array)
317                                 + " now have " + mBaseCacheSize + " entries");
318                     }
319                 }
320             }
321         }
322     }
323 
324     /**
325      * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
326      * will grow once items are added to it.
327      */
ArrayMap()328     public ArrayMap() {
329         this(0, false);
330     }
331 
332     /**
333      * Create a new ArrayMap with a given initial capacity.
334      */
ArrayMap(int capacity)335     public ArrayMap(int capacity) {
336         this(capacity, false);
337     }
338 
339     /** {@hide} */
ArrayMap(int capacity, boolean identityHashCode)340     public ArrayMap(int capacity, boolean identityHashCode) {
341         mIdentityHashCode = identityHashCode;
342 
343         // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
344         // instance instead of the usual EmptyArray.INT. The reference
345         // is checked later to see if the array is allowed to grow.
346         if (capacity < 0) {
347             mHashes = EMPTY_IMMUTABLE_INTS;
348             mArray = EmptyArray.OBJECT;
349         } else if (capacity == 0) {
350             mHashes = EmptyArray.INT;
351             mArray = EmptyArray.OBJECT;
352         } else {
353             allocArrays(capacity);
354         }
355         mSize = 0;
356     }
357 
358     /**
359      * Create a new ArrayMap with the mappings from the given ArrayMap.
360      */
ArrayMap(ArrayMap<K, V> map)361     public ArrayMap(ArrayMap<K, V> map) {
362         this();
363         if (map != null) {
364             putAll(map);
365         }
366     }
367 
368     /**
369      * Make the array map empty.  All storage is released.
370      */
371     @Override
clear()372     public void clear() {
373         if (mSize > 0) {
374             final int[] ohashes = mHashes;
375             final Object[] oarray = mArray;
376             final int osize = mSize;
377             mHashes = EmptyArray.INT;
378             mArray = EmptyArray.OBJECT;
379             mSize = 0;
380             freeArrays(ohashes, oarray, osize);
381         }
382         if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) {
383             throw new ConcurrentModificationException();
384         }
385     }
386 
387     /**
388      * @hide
389      * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
390      */
erase()391     public void erase() {
392         if (mSize > 0) {
393             final int N = mSize<<1;
394             final Object[] array = mArray;
395             for (int i=0; i<N; i++) {
396                 array[i] = null;
397             }
398             mSize = 0;
399         }
400     }
401 
402     /**
403      * Ensure the array map can hold at least <var>minimumCapacity</var>
404      * items.
405      */
ensureCapacity(int minimumCapacity)406     public void ensureCapacity(int minimumCapacity) {
407         final int osize = mSize;
408         if (mHashes.length < minimumCapacity) {
409             final int[] ohashes = mHashes;
410             final Object[] oarray = mArray;
411             allocArrays(minimumCapacity);
412             if (mSize > 0) {
413                 System.arraycopy(ohashes, 0, mHashes, 0, osize);
414                 System.arraycopy(oarray, 0, mArray, 0, osize<<1);
415             }
416             freeArrays(ohashes, oarray, osize);
417         }
418         if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
419             throw new ConcurrentModificationException();
420         }
421     }
422 
423     /**
424      * Check whether a key exists in the array.
425      *
426      * @param key The key to search for.
427      * @return Returns true if the key exists, else false.
428      */
429     @Override
containsKey(Object key)430     public boolean containsKey(Object key) {
431         return indexOfKey(key) >= 0;
432     }
433 
434     /**
435      * Returns the index of a key in the set.
436      *
437      * @param key The key to search for.
438      * @return Returns the index of the key if it exists, else a negative integer.
439      */
indexOfKey(Object key)440     public int indexOfKey(Object key) {
441         return key == null ? indexOfNull()
442                 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
443     }
444 
445     /**
446      * Returns an index for which {@link #valueAt} would return the
447      * specified value, or a negative number if no keys map to the
448      * specified value.
449      * Beware that this is a linear search, unlike lookups by key,
450      * and that multiple keys can map to the same value and this will
451      * find only one of them.
452      */
indexOfValue(Object value)453     public int indexOfValue(Object value) {
454         final int N = mSize*2;
455         final Object[] array = mArray;
456         if (value == null) {
457             for (int i=1; i<N; i+=2) {
458                 if (array[i] == null) {
459                     return i>>1;
460                 }
461             }
462         } else {
463             for (int i=1; i<N; i+=2) {
464                 if (value.equals(array[i])) {
465                     return i>>1;
466                 }
467             }
468         }
469         return -1;
470     }
471 
472     /**
473      * Check whether a value exists in the array.  This requires a linear search
474      * through the entire array.
475      *
476      * @param value The value to search for.
477      * @return Returns true if the value exists, else false.
478      */
479     @Override
containsValue(Object value)480     public boolean containsValue(Object value) {
481         return indexOfValue(value) >= 0;
482     }
483 
484     /**
485      * Retrieve a value from the array.
486      * @param key The key of the value to retrieve.
487      * @return Returns the value associated with the given key,
488      * or null if there is no such key.
489      */
490     @Override
get(Object key)491     public V get(Object key) {
492         final int index = indexOfKey(key);
493         return index >= 0 ? (V)mArray[(index<<1)+1] : null;
494     }
495 
496     /**
497      * Return the key at the given index in the array.
498      *
499      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
500      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
501      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
502      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
503      *
504      * @param index The desired index, must be between 0 and {@link #size()}-1.
505      * @return Returns the key stored at the given index.
506      */
keyAt(int index)507     public K keyAt(int index) {
508         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
509             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
510             // Check if exception should be thrown outside of the critical path.
511             throw new ArrayIndexOutOfBoundsException(index);
512         }
513         return (K)mArray[index << 1];
514     }
515 
516     /**
517      * Return the value at the given index in the array.
518      *
519      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
520      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
521      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
522      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
523      *
524      * @param index The desired index, must be between 0 and {@link #size()}-1.
525      * @return Returns the value stored at the given index.
526      */
valueAt(int index)527     public V valueAt(int index) {
528         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
529             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
530             // Check if exception should be thrown outside of the critical path.
531             throw new ArrayIndexOutOfBoundsException(index);
532         }
533         return (V)mArray[(index << 1) + 1];
534     }
535 
536     /**
537      * Set the value at a given index in the array.
538      *
539      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
540      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
541      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
542      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
543      *
544      * @param index The desired index, must be between 0 and {@link #size()}-1.
545      * @param value The new value to store at this index.
546      * @return Returns the previous value at the given index.
547      */
setValueAt(int index, V value)548     public V setValueAt(int index, V value) {
549         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
550             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
551             // Check if exception should be thrown outside of the critical path.
552             throw new ArrayIndexOutOfBoundsException(index);
553         }
554         index = (index << 1) + 1;
555         V old = (V)mArray[index];
556         mArray[index] = value;
557         return old;
558     }
559 
560     /**
561      * Return true if the array map contains no items.
562      */
563     @Override
isEmpty()564     public boolean isEmpty() {
565         return mSize <= 0;
566     }
567 
568     /**
569      * Add a new value to the array map.
570      * @param key The key under which to store the value.  If
571      * this key already exists in the array, its value will be replaced.
572      * @param value The value to store for the given key.
573      * @return Returns the old value that was stored for the given key, or null if there
574      * was no such key.
575      */
576     @Override
put(K key, V value)577     public V put(K key, V value) {
578         final int osize = mSize;
579         final int hash;
580         int index;
581         if (key == null) {
582             hash = 0;
583             index = indexOfNull();
584         } else {
585             hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
586             index = indexOf(key, hash);
587         }
588         if (index >= 0) {
589             index = (index<<1) + 1;
590             final V old = (V)mArray[index];
591             mArray[index] = value;
592             return old;
593         }
594 
595         index = ~index;
596         if (osize >= mHashes.length) {
597             final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
598                     : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
599 
600             if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
601 
602             final int[] ohashes = mHashes;
603             final Object[] oarray = mArray;
604             allocArrays(n);
605 
606             if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
607                 throw new ConcurrentModificationException();
608             }
609 
610             if (mHashes.length > 0) {
611                 if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
612                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
613                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
614             }
615 
616             freeArrays(ohashes, oarray, osize);
617         }
618 
619         if (index < osize) {
620             if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
621                     + " to " + (index+1));
622             System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
623             System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
624         }
625 
626         if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
627             if (osize != mSize || index >= mHashes.length) {
628                 throw new ConcurrentModificationException();
629             }
630         }
631         mHashes[index] = hash;
632         mArray[index<<1] = key;
633         mArray[(index<<1)+1] = value;
634         mSize++;
635         return null;
636     }
637 
638     /**
639      * Special fast path for appending items to the end of the array without validation.
640      * The array must already be large enough to contain the item.
641      * @hide
642      */
643     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V).
append(K key, V value)644     public void append(K key, V value) {
645         int index = mSize;
646         final int hash = key == null ? 0
647                 : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
648         if (index >= mHashes.length) {
649             throw new IllegalStateException("Array is full");
650         }
651         if (index > 0 && mHashes[index-1] > hash) {
652             RuntimeException e = new RuntimeException("here");
653             e.fillInStackTrace();
654             Log.w(TAG, "New hash " + hash
655                     + " is before end of array hash " + mHashes[index-1]
656                     + " at index " + index + (DEBUG ? " key " + key : ""), e);
657             put(key, value);
658             return;
659         }
660         mSize = index+1;
661         mHashes[index] = hash;
662         index <<= 1;
663         mArray[index] = key;
664         mArray[index+1] = value;
665     }
666 
667     /**
668      * The use of the {@link #append} function can result in invalid array maps, in particular
669      * an array map where the same key appears multiple times.  This function verifies that
670      * the array map is valid, throwing IllegalArgumentException if a problem is found.  The
671      * main use for this method is validating an array map after unpacking from an IPC, to
672      * protect against malicious callers.
673      * @hide
674      */
validate()675     public void validate() {
676         final int N = mSize;
677         if (N <= 1) {
678             // There can't be dups.
679             return;
680         }
681         int basehash = mHashes[0];
682         int basei = 0;
683         for (int i=1; i<N; i++) {
684             int hash = mHashes[i];
685             if (hash != basehash) {
686                 basehash = hash;
687                 basei = i;
688                 continue;
689             }
690             // We are in a run of entries with the same hash code.  Go backwards through
691             // the array to see if any keys are the same.
692             final Object cur = mArray[i<<1];
693             for (int j=i-1; j>=basei; j--) {
694                 final Object prev = mArray[j<<1];
695                 if (cur == prev) {
696                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
697                 }
698                 if (cur != null && prev != null && cur.equals(prev)) {
699                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
700                 }
701             }
702         }
703     }
704 
705     /**
706      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
707      * @param array The array whose contents are to be retrieved.
708      */
putAll(ArrayMap<? extends K, ? extends V> array)709     public void putAll(ArrayMap<? extends K, ? extends V> array) {
710         final int N = array.mSize;
711         ensureCapacity(mSize + N);
712         if (mSize == 0) {
713             if (N > 0) {
714                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
715                 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
716                 mSize = N;
717             }
718         } else {
719             for (int i=0; i<N; i++) {
720                 put(array.keyAt(i), array.valueAt(i));
721             }
722         }
723     }
724 
725     /**
726      * Remove an existing key from the array map.
727      * @param key The key of the mapping to remove.
728      * @return Returns the value that was stored under the key, or null if there
729      * was no such key.
730      */
731     @Override
remove(Object key)732     public V remove(Object key) {
733         final int index = indexOfKey(key);
734         if (index >= 0) {
735             return removeAt(index);
736         }
737 
738         return null;
739     }
740 
741     /**
742      * Remove the key/value mapping at the given index.
743      *
744      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
745      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
746      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
747      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
748      *
749      * @param index The desired index, must be between 0 and {@link #size()}-1.
750      * @return Returns the value that was stored at this index.
751      */
removeAt(int index)752     public V removeAt(int index) {
753         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
754             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
755             // Check if exception should be thrown outside of the critical path.
756             throw new ArrayIndexOutOfBoundsException(index);
757         }
758 
759         final Object old = mArray[(index << 1) + 1];
760         final int osize = mSize;
761         final int nsize;
762         if (osize <= 1) {
763             // Now empty.
764             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
765             final int[] ohashes = mHashes;
766             final Object[] oarray = mArray;
767             mHashes = EmptyArray.INT;
768             mArray = EmptyArray.OBJECT;
769             freeArrays(ohashes, oarray, osize);
770             nsize = 0;
771         } else {
772             nsize = osize - 1;
773             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
774                 // Shrunk enough to reduce size of arrays.  We don't allow it to
775                 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
776                 // that and BASE_SIZE.
777                 final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
778 
779                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
780 
781                 final int[] ohashes = mHashes;
782                 final Object[] oarray = mArray;
783                 allocArrays(n);
784 
785                 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
786                     throw new ConcurrentModificationException();
787                 }
788 
789                 if (index > 0) {
790                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
791                     System.arraycopy(ohashes, 0, mHashes, 0, index);
792                     System.arraycopy(oarray, 0, mArray, 0, index << 1);
793                 }
794                 if (index < nsize) {
795                     if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
796                             + " to " + index);
797                     System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
798                     System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
799                             (nsize - index) << 1);
800                 }
801             } else {
802                 if (index < nsize) {
803                     if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
804                             + " to " + index);
805                     System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
806                     System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
807                             (nsize - index) << 1);
808                 }
809                 mArray[nsize << 1] = null;
810                 mArray[(nsize << 1) + 1] = null;
811             }
812         }
813         if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
814             throw new ConcurrentModificationException();
815         }
816         mSize = nsize;
817         return (V)old;
818     }
819 
820     /**
821      * Return the number of items in this array map.
822      */
823     @Override
size()824     public int size() {
825         return mSize;
826     }
827 
828     /**
829      * {@inheritDoc}
830      *
831      * <p>This implementation returns false if the object is not a map, or
832      * if the maps have different sizes. Otherwise, for each key in this map,
833      * values of both maps are compared. If the values for any key are not
834      * equal, the method returns false, otherwise it returns true.
835      */
836     @Override
equals(@ullable Object object)837     public boolean equals(@Nullable Object object) {
838         if (this == object) {
839             return true;
840         }
841         if (object instanceof Map) {
842             Map<?, ?> map = (Map<?, ?>) object;
843             if (size() != map.size()) {
844                 return false;
845             }
846 
847             try {
848                 for (int i=0; i<mSize; i++) {
849                     K key = keyAt(i);
850                     V mine = valueAt(i);
851                     Object theirs = map.get(key);
852                     if (mine == null) {
853                         if (theirs != null || !map.containsKey(key)) {
854                             return false;
855                         }
856                     } else if (!mine.equals(theirs)) {
857                         return false;
858                     }
859                 }
860             } catch (NullPointerException ignored) {
861                 return false;
862             } catch (ClassCastException ignored) {
863                 return false;
864             }
865             return true;
866         }
867         return false;
868     }
869 
870     /**
871      * {@inheritDoc}
872      */
873     @Override
hashCode()874     public int hashCode() {
875         final int[] hashes = mHashes;
876         final Object[] array = mArray;
877         int result = 0;
878         for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
879             Object value = array[v];
880             result += hashes[i] ^ (value == null ? 0 : value.hashCode());
881         }
882         return result;
883     }
884 
885     /**
886      * {@inheritDoc}
887      *
888      * <p>This implementation composes a string by iterating over its mappings. If
889      * this map contains itself as a key or a value, the string "(this Map)"
890      * will appear in its place.
891      */
892     @Override
toString()893     public String toString() {
894         if (isEmpty()) {
895             return "{}";
896         }
897 
898         StringBuilder buffer = new StringBuilder(mSize * 28);
899         buffer.append('{');
900         for (int i=0; i<mSize; i++) {
901             if (i > 0) {
902                 buffer.append(", ");
903             }
904             Object key = keyAt(i);
905             if (key != this) {
906                 buffer.append(key);
907             } else {
908                 buffer.append("(this Map)");
909             }
910             buffer.append('=');
911             Object value = valueAt(i);
912             if (value != this) {
913                 buffer.append(ArrayUtils.deepToString(value));
914             } else {
915                 buffer.append("(this Map)");
916             }
917         }
918         buffer.append('}');
919         return buffer.toString();
920     }
921 
922     // ------------------------------------------------------------------------
923     // Interop with traditional Java containers.  Not as efficient as using
924     // specialized collection APIs.
925     // ------------------------------------------------------------------------
926 
getCollection()927     private MapCollections<K, V> getCollection() {
928         if (mCollections == null) {
929             mCollections = new MapCollections<K, V>() {
930                 @Override
931                 protected int colGetSize() {
932                     return mSize;
933                 }
934 
935                 @Override
936                 protected Object colGetEntry(int index, int offset) {
937                     return mArray[(index<<1) + offset];
938                 }
939 
940                 @Override
941                 protected int colIndexOfKey(Object key) {
942                     return indexOfKey(key);
943                 }
944 
945                 @Override
946                 protected int colIndexOfValue(Object value) {
947                     return indexOfValue(value);
948                 }
949 
950                 @Override
951                 protected Map<K, V> colGetMap() {
952                     return ArrayMap.this;
953                 }
954 
955                 @Override
956                 protected void colPut(K key, V value) {
957                     put(key, value);
958                 }
959 
960                 @Override
961                 protected V colSetValue(int index, V value) {
962                     return setValueAt(index, value);
963                 }
964 
965                 @Override
966                 protected void colRemoveAt(int index) {
967                     removeAt(index);
968                 }
969 
970                 @Override
971                 protected void colClear() {
972                     clear();
973                 }
974             };
975         }
976         return mCollections;
977     }
978 
979     /**
980      * Determine if the array map contains all of the keys in the given collection.
981      * @param collection The collection whose contents are to be checked against.
982      * @return Returns true if this array map contains a key for every entry
983      * in <var>collection</var>, else returns false.
984      */
containsAll(Collection<?> collection)985     public boolean containsAll(Collection<?> collection) {
986         return MapCollections.containsAllHelper(this, collection);
987     }
988 
989     /**
990      * Performs the given action for all elements in the stored order. This implementation overrides
991      * the default implementation to avoid iterating using the {@link #entrySet()} and iterates in
992      * the key-value order consistent with {@link #keyAt(int)} and {@link #valueAt(int)}.
993      *
994      * @param action The action to be performed for each element
995      */
996     @Override
forEach(BiConsumer<? super K, ? super V> action)997     public void forEach(BiConsumer<? super K, ? super V> action) {
998         if (action == null) {
999             throw new NullPointerException("action must not be null");
1000         }
1001 
1002         final int size = mSize;
1003         for (int i = 0; i < size; ++i) {
1004             if (size != mSize) {
1005                 throw new ConcurrentModificationException();
1006             }
1007             action.accept(keyAt(i), valueAt(i));
1008         }
1009     }
1010 
1011     /**
1012      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
1013      * @param map The map whose contents are to be retrieved.
1014      */
1015     @Override
putAll(Map<? extends K, ? extends V> map)1016     public void putAll(Map<? extends K, ? extends V> map) {
1017         ensureCapacity(mSize + map.size());
1018         for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
1019             put(entry.getKey(), entry.getValue());
1020         }
1021     }
1022 
1023     /**
1024      * Remove all keys in the array map that exist in the given collection.
1025      * @param collection The collection whose contents are to be used to remove keys.
1026      * @return Returns true if any keys were removed from the array map, else false.
1027      */
removeAll(Collection<?> collection)1028     public boolean removeAll(Collection<?> collection) {
1029         return MapCollections.removeAllHelper(this, collection);
1030     }
1031 
1032     /**
1033      * Replaces each entry's value with the result of invoking the given function on that entry
1034      * until all entries have been processed or the function throws an exception. Exceptions thrown
1035      * by the function are relayed to the caller. This implementation overrides
1036      * the default implementation to avoid iterating using the {@link #entrySet()} and iterates in
1037      * the key-value order consistent with {@link #keyAt(int)} and {@link #valueAt(int)}.
1038      *
1039      * @param function The function to apply to each entry
1040      */
1041     @Override
replaceAll(BiFunction<? super K, ? super V, ? extends V> function)1042     public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
1043         if (function == null) {
1044             throw new NullPointerException("function must not be null");
1045         }
1046 
1047         final int size = mSize;
1048         try {
1049             for (int i = 0; i < size; ++i) {
1050                 final int valIndex = (i << 1) + 1;
1051                 //noinspection unchecked
1052                 mArray[valIndex] = function.apply((K) mArray[i << 1], (V) mArray[valIndex]);
1053             }
1054         } catch (ArrayIndexOutOfBoundsException e) {
1055             throw new ConcurrentModificationException();
1056         }
1057         if (size != mSize) {
1058             throw new ConcurrentModificationException();
1059         }
1060     }
1061 
1062     /**
1063      * Remove all keys in the array map that do <b>not</b> exist in the given collection.
1064      * @param collection The collection whose contents are to be used to determine which
1065      * keys to keep.
1066      * @return Returns true if any keys were removed from the array map, else false.
1067      */
retainAll(Collection<?> collection)1068     public boolean retainAll(Collection<?> collection) {
1069         return MapCollections.retainAllHelper(this, collection);
1070     }
1071 
1072     /**
1073      * Return a {@link java.util.Set} for iterating over and interacting with all mappings
1074      * in the array map.
1075      *
1076      * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
1077      * requires generating a number of temporary objects and allocates additional state
1078      * information associated with the container that will remain for the life of the container.</p>
1079      *
1080      * <p><b>Note:</b></p> the semantics of this
1081      * Set are subtly different than that of a {@link java.util.HashMap}: most important,
1082      * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
1083      * object that exists for the entire iterator, so you can <b>not</b> hold on to it
1084      * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
1085      */
1086     @Override
entrySet()1087     public Set<Map.Entry<K, V>> entrySet() {
1088         return getCollection().getEntrySet();
1089     }
1090 
1091     /**
1092      * Return a {@link java.util.Set} for iterating over and interacting with all keys
1093      * in the array map.
1094      *
1095      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
1096      * requires generating a number of temporary objects and allocates additional state
1097      * information associated with the container that will remain for the life of the container.</p>
1098      */
1099     @Override
keySet()1100     public Set<K> keySet() {
1101         return getCollection().getKeySet();
1102     }
1103 
1104     /**
1105      * Return a {@link java.util.Collection} for iterating over and interacting with all values
1106      * in the array map.
1107      *
1108      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
1109      * requires generating a number of temporary objects and allocates additional state
1110      * information associated with the container that will remain for the life of the container.</p>
1111      */
1112     @Override
values()1113     public Collection<V> values() {
1114         return getCollection().getValues();
1115     }
1116 }
1117