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