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