• 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.Collection;
27 import java.util.ConcurrentModificationException;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.function.BiConsumer;
31 import java.util.function.BiFunction;
32 
33 /**
34  * ArrayMap is a generic key->value mapping data structure that is
35  * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
36  * It keeps its mappings in an array data structure -- an integer array of hash
37  * codes for each item, and an Object array of the key/value pairs.  This allows it to
38  * avoid having to create an extra object for every entry put in to the map, and it
39  * also tries to control the growth of the size of these arrays more aggressively
40  * (since growing them only requires copying the entries in the array, not rebuilding
41  * a hash map).
42  *
43  * <p>Note that this implementation is not intended to be appropriate for data structures
44  * that may contain large numbers of items.  It is generally slower than a traditional
45  * HashMap, since lookups require a binary search and adds and removes require inserting
46  * and deleting entries in the array.  For containers holding up to hundreds of items,
47  * the performance difference is not significant, less than 50%.</p>
48  *
49  * <p>Because this container is intended to better balance memory use, unlike most other
50  * standard Java containers it will shrink its array as items are removed from it.  Currently
51  * you have no control over this shrinking -- if you set a capacity and then remove an
52  * item, it may reduce the capacity to better match the current size.  In the future an
53  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
54  *
55  * <p>This structure is <b>NOT</b> thread-safe.</p>
56  */
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 " + 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 " + 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) Log.d(TAG, "Storing 2x cache " + array
299                             + " now have " + mTwiceBaseCacheSize + " entries");
300                 }
301             }
302         } else if (hashes.length == BASE_SIZE) {
303             synchronized (sBaseCacheLock) {
304                 if (mBaseCacheSize < CACHE_SIZE) {
305                     array[0] = mBaseCache;
306                     array[1] = hashes;
307                     for (int i=(size<<1)-1; i>=2; i--) {
308                         array[i] = null;
309                     }
310                     mBaseCache = array;
311                     mBaseCacheSize++;
312                     if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
313                             + " now have " + mBaseCacheSize + " entries");
314                 }
315             }
316         }
317     }
318 
319     /**
320      * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
321      * will grow once items are added to it.
322      */
ArrayMap()323     public ArrayMap() {
324         this(0, false);
325     }
326 
327     /**
328      * Create a new ArrayMap with a given initial capacity.
329      */
ArrayMap(int capacity)330     public ArrayMap(int capacity) {
331         this(capacity, false);
332     }
333 
334     /** {@hide} */
ArrayMap(int capacity, boolean identityHashCode)335     public ArrayMap(int capacity, boolean identityHashCode) {
336         mIdentityHashCode = identityHashCode;
337 
338         // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
339         // instance instead of the usual EmptyArray.INT. The reference
340         // is checked later to see if the array is allowed to grow.
341         if (capacity < 0) {
342             mHashes = EMPTY_IMMUTABLE_INTS;
343             mArray = EmptyArray.OBJECT;
344         } else if (capacity == 0) {
345             mHashes = EmptyArray.INT;
346             mArray = EmptyArray.OBJECT;
347         } else {
348             allocArrays(capacity);
349         }
350         mSize = 0;
351     }
352 
353     /**
354      * Create a new ArrayMap with the mappings from the given ArrayMap.
355      */
ArrayMap(ArrayMap<K, V> map)356     public ArrayMap(ArrayMap<K, V> map) {
357         this();
358         if (map != null) {
359             putAll(map);
360         }
361     }
362 
363     /**
364      * Make the array map empty.  All storage is released.
365      */
366     @Override
clear()367     public void clear() {
368         if (mSize > 0) {
369             final int[] ohashes = mHashes;
370             final Object[] oarray = mArray;
371             final int osize = mSize;
372             mHashes = EmptyArray.INT;
373             mArray = EmptyArray.OBJECT;
374             mSize = 0;
375             freeArrays(ohashes, oarray, osize);
376         }
377         if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) {
378             throw new ConcurrentModificationException();
379         }
380     }
381 
382     /**
383      * @hide
384      * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
385      */
erase()386     public void erase() {
387         if (mSize > 0) {
388             final int N = mSize<<1;
389             final Object[] array = mArray;
390             for (int i=0; i<N; i++) {
391                 array[i] = null;
392             }
393             mSize = 0;
394         }
395     }
396 
397     /**
398      * Ensure the array map can hold at least <var>minimumCapacity</var>
399      * items.
400      */
ensureCapacity(int minimumCapacity)401     public void ensureCapacity(int minimumCapacity) {
402         final int osize = mSize;
403         if (mHashes.length < minimumCapacity) {
404             final int[] ohashes = mHashes;
405             final Object[] oarray = mArray;
406             allocArrays(minimumCapacity);
407             if (mSize > 0) {
408                 System.arraycopy(ohashes, 0, mHashes, 0, osize);
409                 System.arraycopy(oarray, 0, mArray, 0, osize<<1);
410             }
411             freeArrays(ohashes, oarray, osize);
412         }
413         if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
414             throw new ConcurrentModificationException();
415         }
416     }
417 
418     /**
419      * Check whether a key exists in the array.
420      *
421      * @param key The key to search for.
422      * @return Returns true if the key exists, else false.
423      */
424     @Override
containsKey(Object key)425     public boolean containsKey(Object key) {
426         return indexOfKey(key) >= 0;
427     }
428 
429     /**
430      * Returns the index of a key in the set.
431      *
432      * @param key The key to search for.
433      * @return Returns the index of the key if it exists, else a negative integer.
434      */
indexOfKey(Object key)435     public int indexOfKey(Object key) {
436         return key == null ? indexOfNull()
437                 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
438     }
439 
440     /**
441      * Returns an index for which {@link #valueAt} would return the
442      * specified value, or a negative number if no keys map to the
443      * specified value.
444      * Beware that this is a linear search, unlike lookups by key,
445      * and that multiple keys can map to the same value and this will
446      * find only one of them.
447      */
indexOfValue(Object value)448     public int indexOfValue(Object value) {
449         final int N = mSize*2;
450         final Object[] array = mArray;
451         if (value == null) {
452             for (int i=1; i<N; i+=2) {
453                 if (array[i] == null) {
454                     return i>>1;
455                 }
456             }
457         } else {
458             for (int i=1; i<N; i+=2) {
459                 if (value.equals(array[i])) {
460                     return i>>1;
461                 }
462             }
463         }
464         return -1;
465     }
466 
467     /**
468      * Check whether a value exists in the array.  This requires a linear search
469      * through the entire array.
470      *
471      * @param value The value to search for.
472      * @return Returns true if the value exists, else false.
473      */
474     @Override
containsValue(Object value)475     public boolean containsValue(Object value) {
476         return indexOfValue(value) >= 0;
477     }
478 
479     /**
480      * Retrieve a value from the array.
481      * @param key The key of the value to retrieve.
482      * @return Returns the value associated with the given key,
483      * or null if there is no such key.
484      */
485     @Override
get(Object key)486     public V get(Object key) {
487         final int index = indexOfKey(key);
488         return index >= 0 ? (V)mArray[(index<<1)+1] : null;
489     }
490 
491     /**
492      * Return the key at the given index in the array.
493      *
494      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
495      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
496      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
497      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
498      *
499      * @param index The desired index, must be between 0 and {@link #size()}-1.
500      * @return Returns the key stored at the given index.
501      */
keyAt(int index)502     public K keyAt(int index) {
503         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
504             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
505             // Check if exception should be thrown outside of the critical path.
506             throw new ArrayIndexOutOfBoundsException(index);
507         }
508         return (K)mArray[index << 1];
509     }
510 
511     /**
512      * Return the value at the given index in the array.
513      *
514      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
515      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
516      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
517      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
518      *
519      * @param index The desired index, must be between 0 and {@link #size()}-1.
520      * @return Returns the value stored at the given index.
521      */
valueAt(int index)522     public V valueAt(int index) {
523         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
524             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
525             // Check if exception should be thrown outside of the critical path.
526             throw new ArrayIndexOutOfBoundsException(index);
527         }
528         return (V)mArray[(index << 1) + 1];
529     }
530 
531     /**
532      * Set the value at a given index in the array.
533      *
534      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
535      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
536      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
537      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
538      *
539      * @param index The desired index, must be between 0 and {@link #size()}-1.
540      * @param value The new value to store at this index.
541      * @return Returns the previous value at the given index.
542      */
setValueAt(int index, V value)543     public V setValueAt(int index, V value) {
544         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
545             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
546             // Check if exception should be thrown outside of the critical path.
547             throw new ArrayIndexOutOfBoundsException(index);
548         }
549         index = (index << 1) + 1;
550         V old = (V)mArray[index];
551         mArray[index] = value;
552         return old;
553     }
554 
555     /**
556      * Return true if the array map contains no items.
557      */
558     @Override
isEmpty()559     public boolean isEmpty() {
560         return mSize <= 0;
561     }
562 
563     /**
564      * Add a new value to the array map.
565      * @param key The key under which to store the value.  If
566      * this key already exists in the array, its value will be replaced.
567      * @param value The value to store for the given key.
568      * @return Returns the old value that was stored for the given key, or null if there
569      * was no such key.
570      */
571     @Override
put(K key, V value)572     public V put(K key, V value) {
573         final int osize = mSize;
574         final int hash;
575         int index;
576         if (key == null) {
577             hash = 0;
578             index = indexOfNull();
579         } else {
580             hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
581             index = indexOf(key, hash);
582         }
583         if (index >= 0) {
584             index = (index<<1) + 1;
585             final V old = (V)mArray[index];
586             mArray[index] = value;
587             return old;
588         }
589 
590         index = ~index;
591         if (osize >= mHashes.length) {
592             final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
593                     : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
594 
595             if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
596 
597             final int[] ohashes = mHashes;
598             final Object[] oarray = mArray;
599             allocArrays(n);
600 
601             if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
602                 throw new ConcurrentModificationException();
603             }
604 
605             if (mHashes.length > 0) {
606                 if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
607                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
608                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
609             }
610 
611             freeArrays(ohashes, oarray, osize);
612         }
613 
614         if (index < osize) {
615             if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
616                     + " to " + (index+1));
617             System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
618             System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
619         }
620 
621         if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
622             if (osize != mSize || index >= mHashes.length) {
623                 throw new ConcurrentModificationException();
624             }
625         }
626         mHashes[index] = hash;
627         mArray[index<<1] = key;
628         mArray[(index<<1)+1] = value;
629         mSize++;
630         return null;
631     }
632 
633     /**
634      * Special fast path for appending items to the end of the array without validation.
635      * The array must already be large enough to contain the item.
636      * @hide
637      */
638     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V).
append(K key, V value)639     public void append(K key, V value) {
640         int index = mSize;
641         final int hash = key == null ? 0
642                 : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
643         if (index >= mHashes.length) {
644             throw new IllegalStateException("Array is full");
645         }
646         if (index > 0 && mHashes[index-1] > hash) {
647             RuntimeException e = new RuntimeException("here");
648             e.fillInStackTrace();
649             Log.w(TAG, "New hash " + hash
650                     + " is before end of array hash " + mHashes[index-1]
651                     + " at index " + index + (DEBUG ? " key " + key : ""), e);
652             put(key, value);
653             return;
654         }
655         mSize = index+1;
656         mHashes[index] = hash;
657         index <<= 1;
658         mArray[index] = key;
659         mArray[index+1] = value;
660     }
661 
662     /**
663      * The use of the {@link #append} function can result in invalid array maps, in particular
664      * an array map where the same key appears multiple times.  This function verifies that
665      * the array map is valid, throwing IllegalArgumentException if a problem is found.  The
666      * main use for this method is validating an array map after unpacking from an IPC, to
667      * protect against malicious callers.
668      * @hide
669      */
validate()670     public void validate() {
671         final int N = mSize;
672         if (N <= 1) {
673             // There can't be dups.
674             return;
675         }
676         int basehash = mHashes[0];
677         int basei = 0;
678         for (int i=1; i<N; i++) {
679             int hash = mHashes[i];
680             if (hash != basehash) {
681                 basehash = hash;
682                 basei = i;
683                 continue;
684             }
685             // We are in a run of entries with the same hash code.  Go backwards through
686             // the array to see if any keys are the same.
687             final Object cur = mArray[i<<1];
688             for (int j=i-1; j>=basei; j--) {
689                 final Object prev = mArray[j<<1];
690                 if (cur == prev) {
691                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
692                 }
693                 if (cur != null && prev != null && cur.equals(prev)) {
694                     throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
695                 }
696             }
697         }
698     }
699 
700     /**
701      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
702      * @param array The array whose contents are to be retrieved.
703      */
putAll(ArrayMap<? extends K, ? extends V> array)704     public void putAll(ArrayMap<? extends K, ? extends V> array) {
705         final int N = array.mSize;
706         ensureCapacity(mSize + N);
707         if (mSize == 0) {
708             if (N > 0) {
709                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
710                 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
711                 mSize = N;
712             }
713         } else {
714             for (int i=0; i<N; i++) {
715                 put(array.keyAt(i), array.valueAt(i));
716             }
717         }
718     }
719 
720     /**
721      * Remove an existing key from the array map.
722      * @param key The key of the mapping to remove.
723      * @return Returns the value that was stored under the key, or null if there
724      * was no such key.
725      */
726     @Override
remove(Object key)727     public V remove(Object key) {
728         final int index = indexOfKey(key);
729         if (index >= 0) {
730             return removeAt(index);
731         }
732 
733         return null;
734     }
735 
736     /**
737      * Remove the key/value mapping at the given index.
738      *
739      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
740      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
741      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
742      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
743      *
744      * @param index The desired index, must be between 0 and {@link #size()}-1.
745      * @return Returns the value that was stored at this index.
746      */
removeAt(int index)747     public V removeAt(int index) {
748         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
749             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
750             // Check if exception should be thrown outside of the critical path.
751             throw new ArrayIndexOutOfBoundsException(index);
752         }
753 
754         final Object old = mArray[(index << 1) + 1];
755         final int osize = mSize;
756         final int nsize;
757         if (osize <= 1) {
758             // Now empty.
759             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
760             final int[] ohashes = mHashes;
761             final Object[] oarray = mArray;
762             mHashes = EmptyArray.INT;
763             mArray = EmptyArray.OBJECT;
764             freeArrays(ohashes, oarray, osize);
765             nsize = 0;
766         } else {
767             nsize = osize - 1;
768             if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
769                 // Shrunk enough to reduce size of arrays.  We don't allow it to
770                 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
771                 // that and BASE_SIZE.
772                 final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
773 
774                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
775 
776                 final int[] ohashes = mHashes;
777                 final Object[] oarray = mArray;
778                 allocArrays(n);
779 
780                 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
781                     throw new ConcurrentModificationException();
782                 }
783 
784                 if (index > 0) {
785                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
786                     System.arraycopy(ohashes, 0, mHashes, 0, index);
787                     System.arraycopy(oarray, 0, mArray, 0, index << 1);
788                 }
789                 if (index < nsize) {
790                     if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
791                             + " to " + index);
792                     System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
793                     System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
794                             (nsize - index) << 1);
795                 }
796             } else {
797                 if (index < nsize) {
798                     if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
799                             + " to " + index);
800                     System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
801                     System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
802                             (nsize - index) << 1);
803                 }
804                 mArray[nsize << 1] = null;
805                 mArray[(nsize << 1) + 1] = null;
806             }
807         }
808         if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
809             throw new ConcurrentModificationException();
810         }
811         mSize = nsize;
812         return (V)old;
813     }
814 
815     /**
816      * Return the number of items in this array map.
817      */
818     @Override
size()819     public int size() {
820         return mSize;
821     }
822 
823     /**
824      * {@inheritDoc}
825      *
826      * <p>This implementation returns false if the object is not a map, or
827      * if the maps have different sizes. Otherwise, for each key in this map,
828      * values of both maps are compared. If the values for any key are not
829      * equal, the method returns false, otherwise it returns true.
830      */
831     @Override
equals(@ullable Object object)832     public boolean equals(@Nullable Object object) {
833         if (this == object) {
834             return true;
835         }
836         if (object instanceof Map) {
837             Map<?, ?> map = (Map<?, ?>) object;
838             if (size() != map.size()) {
839                 return false;
840             }
841 
842             try {
843                 for (int i=0; i<mSize; i++) {
844                     K key = keyAt(i);
845                     V mine = valueAt(i);
846                     Object theirs = map.get(key);
847                     if (mine == null) {
848                         if (theirs != null || !map.containsKey(key)) {
849                             return false;
850                         }
851                     } else if (!mine.equals(theirs)) {
852                         return false;
853                     }
854                 }
855             } catch (NullPointerException ignored) {
856                 return false;
857             } catch (ClassCastException ignored) {
858                 return false;
859             }
860             return true;
861         }
862         return false;
863     }
864 
865     /**
866      * {@inheritDoc}
867      */
868     @Override
hashCode()869     public int hashCode() {
870         final int[] hashes = mHashes;
871         final Object[] array = mArray;
872         int result = 0;
873         for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
874             Object value = array[v];
875             result += hashes[i] ^ (value == null ? 0 : value.hashCode());
876         }
877         return result;
878     }
879 
880     /**
881      * {@inheritDoc}
882      *
883      * <p>This implementation composes a string by iterating over its mappings. If
884      * this map contains itself as a key or a value, the string "(this Map)"
885      * will appear in its place.
886      */
887     @Override
toString()888     public String toString() {
889         if (isEmpty()) {
890             return "{}";
891         }
892 
893         StringBuilder buffer = new StringBuilder(mSize * 28);
894         buffer.append('{');
895         for (int i=0; i<mSize; i++) {
896             if (i > 0) {
897                 buffer.append(", ");
898             }
899             Object key = keyAt(i);
900             if (key != this) {
901                 buffer.append(key);
902             } else {
903                 buffer.append("(this Map)");
904             }
905             buffer.append('=');
906             Object value = valueAt(i);
907             if (value != this) {
908                 buffer.append(ArrayUtils.deepToString(value));
909             } else {
910                 buffer.append("(this Map)");
911             }
912         }
913         buffer.append('}');
914         return buffer.toString();
915     }
916 
917     // ------------------------------------------------------------------------
918     // Interop with traditional Java containers.  Not as efficient as using
919     // specialized collection APIs.
920     // ------------------------------------------------------------------------
921 
getCollection()922     private MapCollections<K, V> getCollection() {
923         if (mCollections == null) {
924             mCollections = new MapCollections<K, V>() {
925                 @Override
926                 protected int colGetSize() {
927                     return mSize;
928                 }
929 
930                 @Override
931                 protected Object colGetEntry(int index, int offset) {
932                     return mArray[(index<<1) + offset];
933                 }
934 
935                 @Override
936                 protected int colIndexOfKey(Object key) {
937                     return indexOfKey(key);
938                 }
939 
940                 @Override
941                 protected int colIndexOfValue(Object value) {
942                     return indexOfValue(value);
943                 }
944 
945                 @Override
946                 protected Map<K, V> colGetMap() {
947                     return ArrayMap.this;
948                 }
949 
950                 @Override
951                 protected void colPut(K key, V value) {
952                     put(key, value);
953                 }
954 
955                 @Override
956                 protected V colSetValue(int index, V value) {
957                     return setValueAt(index, value);
958                 }
959 
960                 @Override
961                 protected void colRemoveAt(int index) {
962                     removeAt(index);
963                 }
964 
965                 @Override
966                 protected void colClear() {
967                     clear();
968                 }
969             };
970         }
971         return mCollections;
972     }
973 
974     /**
975      * Determine if the array map contains all of the keys in the given collection.
976      * @param collection The collection whose contents are to be checked against.
977      * @return Returns true if this array map contains a key for every entry
978      * in <var>collection</var>, else returns false.
979      */
containsAll(Collection<?> collection)980     public boolean containsAll(Collection<?> collection) {
981         return MapCollections.containsAllHelper(this, collection);
982     }
983 
984     /**
985      * Performs the given action for all elements in the stored order. This implementation overrides
986      * the default implementation to avoid iterating using the {@link #entrySet()} and iterates in
987      * the key-value order consistent with {@link #keyAt(int)} and {@link #valueAt(int)}.
988      *
989      * @param action The action to be performed for each element
990      */
991     @Override
forEach(BiConsumer<? super K, ? super V> action)992     public void forEach(BiConsumer<? super K, ? super V> action) {
993         if (action == null) {
994             throw new NullPointerException("action must not be null");
995         }
996 
997         final int size = mSize;
998         for (int i = 0; i < size; ++i) {
999             if (size != mSize) {
1000                 throw new ConcurrentModificationException();
1001             }
1002             action.accept(keyAt(i), valueAt(i));
1003         }
1004     }
1005 
1006     /**
1007      * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
1008      * @param map The map whose contents are to be retrieved.
1009      */
1010     @Override
putAll(Map<? extends K, ? extends V> map)1011     public void putAll(Map<? extends K, ? extends V> map) {
1012         ensureCapacity(mSize + map.size());
1013         for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
1014             put(entry.getKey(), entry.getValue());
1015         }
1016     }
1017 
1018     /**
1019      * Remove all keys in the array map that exist in the given collection.
1020      * @param collection The collection whose contents are to be used to remove keys.
1021      * @return Returns true if any keys were removed from the array map, else false.
1022      */
removeAll(Collection<?> collection)1023     public boolean removeAll(Collection<?> collection) {
1024         return MapCollections.removeAllHelper(this, collection);
1025     }
1026 
1027     /**
1028      * Replaces each entry's value with the result of invoking the given function on that entry
1029      * until all entries have been processed or the function throws an exception. Exceptions thrown
1030      * by the function are relayed to the caller. This implementation overrides
1031      * the default implementation to avoid iterating using the {@link #entrySet()} and iterates in
1032      * the key-value order consistent with {@link #keyAt(int)} and {@link #valueAt(int)}.
1033      *
1034      * @param function The function to apply to each entry
1035      */
1036     @Override
replaceAll(BiFunction<? super K, ? super V, ? extends V> function)1037     public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
1038         if (function == null) {
1039             throw new NullPointerException("function must not be null");
1040         }
1041 
1042         final int size = mSize;
1043         try {
1044             for (int i = 0; i < size; ++i) {
1045                 final int valIndex = (i << 1) + 1;
1046                 //noinspection unchecked
1047                 mArray[valIndex] = function.apply((K) mArray[i << 1], (V) mArray[valIndex]);
1048             }
1049         } catch (ArrayIndexOutOfBoundsException e) {
1050             throw new ConcurrentModificationException();
1051         }
1052         if (size != mSize) {
1053             throw new ConcurrentModificationException();
1054         }
1055     }
1056 
1057     /**
1058      * Remove all keys in the array map that do <b>not</b> exist in the given collection.
1059      * @param collection The collection whose contents are to be used to determine which
1060      * keys to keep.
1061      * @return Returns true if any keys were removed from the array map, else false.
1062      */
retainAll(Collection<?> collection)1063     public boolean retainAll(Collection<?> collection) {
1064         return MapCollections.retainAllHelper(this, collection);
1065     }
1066 
1067     /**
1068      * Return a {@link java.util.Set} for iterating over and interacting with all mappings
1069      * in the array map.
1070      *
1071      * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
1072      * requires generating a number of temporary objects and allocates additional state
1073      * information associated with the container that will remain for the life of the container.</p>
1074      *
1075      * <p><b>Note:</b></p> the semantics of this
1076      * Set are subtly different than that of a {@link java.util.HashMap}: most important,
1077      * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
1078      * object that exists for the entire iterator, so you can <b>not</b> hold on to it
1079      * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
1080      */
1081     @Override
entrySet()1082     public Set<Map.Entry<K, V>> entrySet() {
1083         return getCollection().getEntrySet();
1084     }
1085 
1086     /**
1087      * Return a {@link java.util.Set} for iterating over and interacting with all keys
1088      * in the array map.
1089      *
1090      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
1091      * requires generating a number of temporary objects and allocates additional state
1092      * information associated with the container that will remain for the life of the container.</p>
1093      */
1094     @Override
keySet()1095     public Set<K> keySet() {
1096         return getCollection().getKeySet();
1097     }
1098 
1099     /**
1100      * Return a {@link java.util.Collection} for iterating over and interacting with all values
1101      * in the array map.
1102      *
1103      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
1104      * requires generating a number of temporary objects and allocates additional state
1105      * information associated with the container that will remain for the life of the container.</p>
1106      */
1107     @Override
values()1108     public Collection<V> values() {
1109         return getCollection().getValues();
1110     }
1111 }
1112