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