blob: e2af6f5ed102e074823a8f605646217ac36daa67 [file] [log] [blame]
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001/*
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
17package android.util;
18
Mathew Inwood4eb56ab2018-08-14 17:24:32 +010019import android.annotation.UnsupportedAppUsage;
Jeff Sharkeybffd2502019-02-28 16:39:12 -070020
21import com.android.internal.util.ArrayUtils;
22
Kweku Adams17d453e2019-03-05 15:30:42 -080023import libcore.util.EmptyArray;
24
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070025import java.util.Collection;
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -040026import java.util.ConcurrentModificationException;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070027import java.util.Map;
28import 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,
Dianne Hackbornb993f412013-07-12 17:46:45 -070044 * the performance difference is not significant, less than 50%.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070045 *
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070046 * <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
Dianne Hackbornb993f412013-07-12 17:46:45 -070050 * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070051 */
52public final class ArrayMap<K, V> implements Map<K, V> {
53 private static final boolean DEBUG = false;
54 private static final String TAG = "ArrayMap";
55
56 /**
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -040057 * Attempt to spot concurrent modifications to this data structure.
58 *
59 * It's best-effort, but any time we can throw something more diagnostic than an
60 * ArrayIndexOutOfBoundsException deep in the ArrayMap internals it's going to
61 * save a lot of development time.
62 *
63 * Good times to look for CME include after any allocArrays() call and at the end of
64 * functions that change mSize (put/remove/clear).
65 */
66 private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true;
67
68 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070069 * The minimum amount by which the capacity of a ArrayMap will increase.
70 * This is tuned to be relatively space-efficient.
71 */
72 private static final int BASE_SIZE = 4;
73
74 /**
75 * Maximum number of entries to have in array caches.
76 */
Jake Whartona8a04352018-09-29 01:52:24 -040077 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070078 private static final int CACHE_SIZE = 10;
79
80 /**
Dianne Hackborn894ce602015-12-04 13:25:48 -080081 * Special hash array value that indicates the container is immutable.
82 */
Jake Whartona8a04352018-09-29 01:52:24 -040083 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackborn894ce602015-12-04 13:25:48 -080084 static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
85
86 /**
Dianne Hackbornb87655b2013-07-17 19:06:22 -070087 * @hide Special immutable empty ArrayMap.
88 */
Jake Whartona8a04352018-09-29 01:52:24 -040089 @UnsupportedAppUsage(maxTargetSdk = 28) // Use your own singleton empty map.
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070090 public static final ArrayMap EMPTY = new ArrayMap<>(-1);
Dianne Hackbornb87655b2013-07-17 19:06:22 -070091
92 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070093 * Caches of small array objects to avoid spamming garbage. The cache
94 * Object[] variable is a pointer to a linked list of array objects.
95 * The first entry in the array is a pointer to the next array in the
96 * list; the second entry is a pointer to the int[] hash code array for it.
97 */
Jake Whartona8a04352018-09-29 01:52:24 -040098 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070099 static Object[] mBaseCache;
Jake Whartona8a04352018-09-29 01:52:24 -0400100 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700101 static int mBaseCacheSize;
Jake Whartona8a04352018-09-29 01:52:24 -0400102 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700103 static Object[] mTwiceBaseCache;
Jake Whartona8a04352018-09-29 01:52:24 -0400104 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700105 static int mTwiceBaseCacheSize;
106
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700107 final boolean mIdentityHashCode;
Jake Whartona8a04352018-09-29 01:52:24 -0400108 @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700109 int[] mHashes;
Jake Whartona8a04352018-09-29 01:52:24 -0400110 @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700111 Object[] mArray;
Jake Whartona8a04352018-09-29 01:52:24 -0400112 @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700113 int mSize;
114 MapCollections<K, V> mCollections;
115
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400116 private static int binarySearchHashes(int[] hashes, int N, int hash) {
117 try {
118 return ContainerHelpers.binarySearch(hashes, N, hash);
119 } catch (ArrayIndexOutOfBoundsException e) {
120 if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
121 throw new ConcurrentModificationException();
122 } else {
123 throw e; // the cache is poisoned at this point, there's not much we can do
124 }
125 }
126 }
127
Jake Whartona8a04352018-09-29 01:52:24 -0400128 @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700129 int indexOf(Object key, int hash) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700130 final int N = mSize;
131
132 // Important fast case: if nothing is in here, nothing to look for.
133 if (N == 0) {
134 return ~0;
135 }
136
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400137 int index = binarySearchHashes(mHashes, N, hash);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700138
139 // If the hash code wasn't found, then we have no entry for this key.
140 if (index < 0) {
141 return index;
142 }
143
144 // If the key at the returned index matches, that's what we want.
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700145 if (key.equals(mArray[index<<1])) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700146 return index;
147 }
148
149 // Search for a matching key after the index.
150 int end;
151 for (end = index + 1; end < N && mHashes[end] == hash; end++) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700152 if (key.equals(mArray[end << 1])) return end;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700153 }
154
155 // Search for a matching key before the index.
156 for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700157 if (key.equals(mArray[i << 1])) return i;
158 }
159
160 // Key not found -- return negative value indicating where a
161 // new entry for this key should go. We use the end of the
162 // hash chain to reduce the number of array entries that will
163 // need to be copied when inserting.
164 return ~end;
165 }
166
Jake Whartona8a04352018-09-29 01:52:24 -0400167 @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700168 int indexOfNull() {
169 final int N = mSize;
170
171 // Important fast case: if nothing is in here, nothing to look for.
172 if (N == 0) {
173 return ~0;
174 }
175
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400176 int index = binarySearchHashes(mHashes, N, 0);
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700177
178 // If the hash code wasn't found, then we have no entry for this key.
179 if (index < 0) {
180 return index;
181 }
182
183 // If the key at the returned index matches, that's what we want.
184 if (null == mArray[index<<1]) {
185 return index;
186 }
187
188 // Search for a matching key after the index.
189 int end;
190 for (end = index + 1; end < N && mHashes[end] == 0; end++) {
191 if (null == mArray[end << 1]) return end;
192 }
193
194 // Search for a matching key before the index.
195 for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
196 if (null == mArray[i << 1]) return i;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700197 }
198
199 // Key not found -- return negative value indicating where a
200 // new entry for this key should go. We use the end of the
201 // hash chain to reduce the number of array entries that will
202 // need to be copied when inserting.
203 return ~end;
204 }
205
Jake Whartona8a04352018-09-29 01:52:24 -0400206 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700207 private void allocArrays(final int size) {
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700208 if (mHashes == EMPTY_IMMUTABLE_INTS) {
209 throw new UnsupportedOperationException("ArrayMap is immutable");
210 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700211 if (size == (BASE_SIZE*2)) {
212 synchronized (ArrayMap.class) {
213 if (mTwiceBaseCache != null) {
214 final Object[] array = mTwiceBaseCache;
215 mArray = array;
216 mTwiceBaseCache = (Object[])array[0];
217 mHashes = (int[])array[1];
218 array[0] = array[1] = null;
219 mTwiceBaseCacheSize--;
220 if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
221 + " now have " + mTwiceBaseCacheSize + " entries");
222 return;
223 }
224 }
225 } else if (size == BASE_SIZE) {
226 synchronized (ArrayMap.class) {
227 if (mBaseCache != null) {
228 final Object[] array = mBaseCache;
229 mArray = array;
230 mBaseCache = (Object[])array[0];
231 mHashes = (int[])array[1];
232 array[0] = array[1] = null;
233 mBaseCacheSize--;
234 if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
235 + " now have " + mBaseCacheSize + " entries");
236 return;
237 }
238 }
239 }
240
241 mHashes = new int[size];
242 mArray = new Object[size<<1];
243 }
244
Jake Whartona8a04352018-09-29 01:52:24 -0400245 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700246 private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
247 if (hashes.length == (BASE_SIZE*2)) {
248 synchronized (ArrayMap.class) {
249 if (mTwiceBaseCacheSize < CACHE_SIZE) {
250 array[0] = mTwiceBaseCache;
251 array[1] = hashes;
252 for (int i=(size<<1)-1; i>=2; i--) {
253 array[i] = null;
254 }
255 mTwiceBaseCache = array;
256 mTwiceBaseCacheSize++;
257 if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
258 + " now have " + mTwiceBaseCacheSize + " entries");
259 }
260 }
261 } else if (hashes.length == BASE_SIZE) {
262 synchronized (ArrayMap.class) {
263 if (mBaseCacheSize < CACHE_SIZE) {
264 array[0] = mBaseCache;
265 array[1] = hashes;
266 for (int i=(size<<1)-1; i>=2; i--) {
267 array[i] = null;
268 }
269 mBaseCache = array;
270 mBaseCacheSize++;
271 if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
272 + " now have " + mBaseCacheSize + " entries");
273 }
274 }
275 }
276 }
277
278 /**
279 * Create a new empty ArrayMap. The default capacity of an array map is 0, and
280 * will grow once items are added to it.
281 */
282 public ArrayMap() {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700283 this(0, false);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700284 }
285
286 /**
287 * Create a new ArrayMap with a given initial capacity.
288 */
289 public ArrayMap(int capacity) {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700290 this(capacity, false);
291 }
292
293 /** {@hide} */
294 public ArrayMap(int capacity, boolean identityHashCode) {
295 mIdentityHashCode = identityHashCode;
296
297 // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
298 // instance instead of the usual EmptyArray.INT. The reference
299 // is checked later to see if the array is allowed to grow.
300 if (capacity < 0) {
301 mHashes = EMPTY_IMMUTABLE_INTS;
302 mArray = EmptyArray.OBJECT;
303 } else if (capacity == 0) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500304 mHashes = EmptyArray.INT;
305 mArray = EmptyArray.OBJECT;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700306 } else {
307 allocArrays(capacity);
308 }
309 mSize = 0;
310 }
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700311
Chet Haase08735182013-06-04 10:44:40 -0700312 /**
313 * Create a new ArrayMap with the mappings from the given ArrayMap.
314 */
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700315 public ArrayMap(ArrayMap<K, V> map) {
Chet Haase08735182013-06-04 10:44:40 -0700316 this();
317 if (map != null) {
318 putAll(map);
319 }
320 }
321
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700322 /**
323 * Make the array map empty. All storage is released.
324 */
325 @Override
326 public void clear() {
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700327 if (mSize > 0) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400328 final int[] ohashes = mHashes;
329 final Object[] oarray = mArray;
330 final int osize = mSize;
Adam Lesinski776abc22014-03-07 11:30:59 -0500331 mHashes = EmptyArray.INT;
332 mArray = EmptyArray.OBJECT;
Dianne Hackborn390517b2013-05-30 15:03:32 -0700333 mSize = 0;
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400334 freeArrays(ohashes, oarray, osize);
335 }
336 if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) {
337 throw new ConcurrentModificationException();
Dianne Hackborn390517b2013-05-30 15:03:32 -0700338 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700339 }
340
341 /**
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700342 * @hide
343 * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
344 */
345 public void erase() {
346 if (mSize > 0) {
347 final int N = mSize<<1;
348 final Object[] array = mArray;
349 for (int i=0; i<N; i++) {
350 array[i] = null;
351 }
Dianne Hackborn4a7d8242013-10-03 10:19:20 -0700352 mSize = 0;
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700353 }
354 }
355
356 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700357 * Ensure the array map can hold at least <var>minimumCapacity</var>
358 * items.
359 */
360 public void ensureCapacity(int minimumCapacity) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400361 final int osize = mSize;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700362 if (mHashes.length < minimumCapacity) {
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700363 final int[] ohashes = mHashes;
364 final Object[] oarray = mArray;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700365 allocArrays(minimumCapacity);
Dianne Hackborn390517b2013-05-30 15:03:32 -0700366 if (mSize > 0) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400367 System.arraycopy(ohashes, 0, mHashes, 0, osize);
368 System.arraycopy(oarray, 0, mArray, 0, osize<<1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700369 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400370 freeArrays(ohashes, oarray, osize);
371 }
372 if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
373 throw new ConcurrentModificationException();
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700374 }
375 }
376
377 /**
378 * Check whether a key exists in the array.
379 *
380 * @param key The key to search for.
381 * @return Returns true if the key exists, else false.
382 */
383 @Override
384 public boolean containsKey(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700385 return indexOfKey(key) >= 0;
386 }
387
388 /**
389 * Returns the index of a key in the set.
390 *
391 * @param key The key to search for.
392 * @return Returns the index of the key if it exists, else a negative integer.
393 */
394 public int indexOfKey(Object key) {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700395 return key == null ? indexOfNull()
396 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700397 }
398
Jake Whartona8a04352018-09-29 01:52:24 -0400399 /**
400 * Returns an index for which {@link #valueAt} would return the
401 * specified value, or a negative number if no keys map to the
402 * specified value.
403 * Beware that this is a linear search, unlike lookups by key,
404 * and that multiple keys can map to the same value and this will
405 * find only one of them.
406 */
407 public int indexOfValue(Object value) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700408 final int N = mSize*2;
409 final Object[] array = mArray;
410 if (value == null) {
411 for (int i=1; i<N; i+=2) {
412 if (array[i] == null) {
413 return i>>1;
414 }
415 }
416 } else {
417 for (int i=1; i<N; i+=2) {
418 if (value.equals(array[i])) {
419 return i>>1;
420 }
421 }
422 }
423 return -1;
424 }
425
426 /**
427 * Check whether a value exists in the array. This requires a linear search
428 * through the entire array.
429 *
430 * @param value The value to search for.
431 * @return Returns true if the value exists, else false.
432 */
433 @Override
434 public boolean containsValue(Object value) {
435 return indexOfValue(value) >= 0;
436 }
437
438 /**
439 * Retrieve a value from the array.
440 * @param key The key of the value to retrieve.
441 * @return Returns the value associated with the given key,
442 * or null if there is no such key.
443 */
444 @Override
445 public V get(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700446 final int index = indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700447 return index >= 0 ? (V)mArray[(index<<1)+1] : null;
448 }
449
450 /**
451 * Return the key at the given index in the array.
452 * @param index The desired index, must be between 0 and {@link #size()}-1.
453 * @return Returns the key stored at the given index.
454 */
455 public K keyAt(int index) {
Kweku Adams17d453e2019-03-05 15:30:42 -0800456 if (index >= mSize) {
457 // The array might be slightly bigger than mSize, in which case, indexing won't fail.
458 throw new ArrayIndexOutOfBoundsException(index);
459 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700460 return (K)mArray[index << 1];
461 }
462
463 /**
464 * Return the value at the given index in the array.
465 * @param index The desired index, must be between 0 and {@link #size()}-1.
466 * @return Returns the value stored at the given index.
467 */
468 public V valueAt(int index) {
Kweku Adams17d453e2019-03-05 15:30:42 -0800469 if (index >= mSize) {
470 // The array might be slightly bigger than mSize, in which case, indexing won't fail.
471 throw new ArrayIndexOutOfBoundsException(index);
472 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700473 return (V)mArray[(index << 1) + 1];
474 }
475
476 /**
477 * Set the value at a given index in the array.
478 * @param index The desired index, must be between 0 and {@link #size()}-1.
479 * @param value The new value to store at this index.
480 * @return Returns the previous value at the given index.
481 */
482 public V setValueAt(int index, V value) {
Kweku Adams17d453e2019-03-05 15:30:42 -0800483 if (index >= mSize) {
484 // The array might be slightly bigger than mSize, in which case, indexing won't fail.
485 throw new ArrayIndexOutOfBoundsException(index);
486 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700487 index = (index << 1) + 1;
488 V old = (V)mArray[index];
489 mArray[index] = value;
490 return old;
491 }
492
493 /**
494 * Return true if the array map contains no items.
495 */
496 @Override
497 public boolean isEmpty() {
498 return mSize <= 0;
499 }
500
501 /**
502 * Add a new value to the array map.
Dianne Hackborna3fb40d2014-08-12 15:06:50 -0700503 * @param key The key under which to store the value. If
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700504 * this key already exists in the array, its value will be replaced.
505 * @param value The value to store for the given key.
506 * @return Returns the old value that was stored for the given key, or null if there
507 * was no such key.
508 */
509 @Override
510 public V put(K key, V value) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400511 final int osize = mSize;
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700512 final int hash;
513 int index;
514 if (key == null) {
515 hash = 0;
516 index = indexOfNull();
517 } else {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700518 hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700519 index = indexOf(key, hash);
520 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700521 if (index >= 0) {
522 index = (index<<1) + 1;
523 final V old = (V)mArray[index];
524 mArray[index] = value;
525 return old;
526 }
527
528 index = ~index;
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400529 if (osize >= mHashes.length) {
530 final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
531 : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700532
533 if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
534
535 final int[] ohashes = mHashes;
536 final Object[] oarray = mArray;
537 allocArrays(n);
538
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400539 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
540 throw new ConcurrentModificationException();
541 }
542
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700543 if (mHashes.length > 0) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400544 if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700545 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
546 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
547 }
548
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400549 freeArrays(ohashes, oarray, osize);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700550 }
551
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400552 if (index < osize) {
553 if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700554 + " to " + (index+1));
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400555 System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700556 System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
557 }
558
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400559 if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
560 if (osize != mSize || index >= mHashes.length) {
561 throw new ConcurrentModificationException();
562 }
563 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700564 mHashes[index] = hash;
565 mArray[index<<1] = key;
566 mArray[(index<<1)+1] = value;
567 mSize++;
568 return null;
569 }
570
571 /**
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700572 * Special fast path for appending items to the end of the array without validation.
573 * The array must already be large enough to contain the item.
574 * @hide
575 */
Jake Whartona8a04352018-09-29 01:52:24 -0400576 @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V).
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700577 public void append(K key, V value) {
578 int index = mSize;
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700579 final int hash = key == null ? 0
580 : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700581 if (index >= mHashes.length) {
582 throw new IllegalStateException("Array is full");
583 }
584 if (index > 0 && mHashes[index-1] > hash) {
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700585 RuntimeException e = new RuntimeException("here");
586 e.fillInStackTrace();
587 Log.w(TAG, "New hash " + hash
588 + " is before end of array hash " + mHashes[index-1]
589 + " at index " + index + " key " + key, e);
590 put(key, value);
591 return;
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700592 }
593 mSize = index+1;
594 mHashes[index] = hash;
595 index <<= 1;
596 mArray[index] = key;
597 mArray[index+1] = value;
598 }
599
600 /**
Dianne Hackborn9c3e74f2014-08-13 15:39:50 -0700601 * The use of the {@link #append} function can result in invalid array maps, in particular
602 * an array map where the same key appears multiple times. This function verifies that
603 * the array map is valid, throwing IllegalArgumentException if a problem is found. The
604 * main use for this method is validating an array map after unpacking from an IPC, to
605 * protect against malicious callers.
606 * @hide
607 */
608 public void validate() {
609 final int N = mSize;
610 if (N <= 1) {
611 // There can't be dups.
612 return;
613 }
614 int basehash = mHashes[0];
615 int basei = 0;
616 for (int i=1; i<N; i++) {
617 int hash = mHashes[i];
618 if (hash != basehash) {
619 basehash = hash;
620 basei = i;
621 continue;
622 }
623 // We are in a run of entries with the same hash code. Go backwards through
624 // the array to see if any keys are the same.
625 final Object cur = mArray[i<<1];
626 for (int j=i-1; j>=basei; j--) {
627 final Object prev = mArray[j<<1];
628 if (cur == prev) {
629 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
630 }
631 if (cur != null && prev != null && cur.equals(prev)) {
632 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
633 }
634 }
635 }
636 }
637
638 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700639 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
640 * @param array The array whose contents are to be retrieved.
641 */
642 public void putAll(ArrayMap<? extends K, ? extends V> array) {
643 final int N = array.mSize;
644 ensureCapacity(mSize + N);
Chet Haasef4130cf2013-06-06 16:34:33 -0700645 if (mSize == 0) {
646 if (N > 0) {
647 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
648 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
649 mSize = N;
650 }
651 } else {
652 for (int i=0; i<N; i++) {
653 put(array.keyAt(i), array.valueAt(i));
654 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700655 }
656 }
657
658 /**
659 * Remove an existing key from the array map.
660 * @param key The key of the mapping to remove.
661 * @return Returns the value that was stored under the key, or null if there
662 * was no such key.
663 */
664 @Override
665 public V remove(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700666 final int index = indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700667 if (index >= 0) {
668 return removeAt(index);
669 }
670
671 return null;
672 }
673
674 /**
675 * Remove the key/value mapping at the given index.
676 * @param index The desired index, must be between 0 and {@link #size()}-1.
677 * @return Returns the value that was stored at this index.
678 */
679 public V removeAt(int index) {
Kweku Adams17d453e2019-03-05 15:30:42 -0800680 if (index >= mSize) {
681 // The array might be slightly bigger than mSize, in which case, indexing won't fail.
682 throw new ArrayIndexOutOfBoundsException(index);
683 }
684
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700685 final Object old = mArray[(index << 1) + 1];
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400686 final int osize = mSize;
687 final int nsize;
688 if (osize <= 1) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700689 // Now empty.
690 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
Suprabh Shukla1938e342018-05-14 14:22:11 -0700691 final int[] ohashes = mHashes;
692 final Object[] oarray = mArray;
Adam Lesinski776abc22014-03-07 11:30:59 -0500693 mHashes = EmptyArray.INT;
694 mArray = EmptyArray.OBJECT;
Suprabh Shukla1938e342018-05-14 14:22:11 -0700695 freeArrays(ohashes, oarray, osize);
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400696 nsize = 0;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700697 } else {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400698 nsize = osize - 1;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700699 if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
700 // Shrunk enough to reduce size of arrays. We don't allow it to
701 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
702 // that and BASE_SIZE.
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400703 final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700704
705 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
706
707 final int[] ohashes = mHashes;
708 final Object[] oarray = mArray;
709 allocArrays(n);
710
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400711 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
712 throw new ConcurrentModificationException();
713 }
714
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700715 if (index > 0) {
716 if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
717 System.arraycopy(ohashes, 0, mHashes, 0, index);
718 System.arraycopy(oarray, 0, mArray, 0, index << 1);
719 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400720 if (index < nsize) {
721 if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700722 + " to " + index);
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400723 System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700724 System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400725 (nsize - index) << 1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700726 }
727 } else {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400728 if (index < nsize) {
729 if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700730 + " to " + index);
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400731 System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700732 System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400733 (nsize - index) << 1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700734 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400735 mArray[nsize << 1] = null;
736 mArray[(nsize << 1) + 1] = null;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700737 }
738 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400739 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
740 throw new ConcurrentModificationException();
741 }
742 mSize = nsize;
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700743 return (V)old;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700744 }
745
746 /**
747 * Return the number of items in this array map.
748 */
749 @Override
750 public int size() {
751 return mSize;
752 }
753
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700754 /**
755 * {@inheritDoc}
756 *
757 * <p>This implementation returns false if the object is not a map, or
758 * if the maps have different sizes. Otherwise, for each key in this map,
759 * values of both maps are compared. If the values for any key are not
760 * equal, the method returns false, otherwise it returns true.
761 */
762 @Override
763 public boolean equals(Object object) {
764 if (this == object) {
765 return true;
766 }
767 if (object instanceof Map) {
768 Map<?, ?> map = (Map<?, ?>) object;
769 if (size() != map.size()) {
770 return false;
771 }
772
773 try {
774 for (int i=0; i<mSize; i++) {
775 K key = keyAt(i);
776 V mine = valueAt(i);
777 Object theirs = map.get(key);
778 if (mine == null) {
779 if (theirs != null || !map.containsKey(key)) {
780 return false;
781 }
782 } else if (!mine.equals(theirs)) {
783 return false;
784 }
785 }
786 } catch (NullPointerException ignored) {
787 return false;
788 } catch (ClassCastException ignored) {
789 return false;
790 }
791 return true;
792 }
793 return false;
794 }
795
796 /**
797 * {@inheritDoc}
798 */
799 @Override
800 public int hashCode() {
801 final int[] hashes = mHashes;
802 final Object[] array = mArray;
803 int result = 0;
804 for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
805 Object value = array[v];
806 result += hashes[i] ^ (value == null ? 0 : value.hashCode());
807 }
808 return result;
809 }
810
Dianne Hackborna17c0f52013-06-20 18:59:11 -0700811 /**
812 * {@inheritDoc}
813 *
814 * <p>This implementation composes a string by iterating over its mappings. If
815 * this map contains itself as a key or a value, the string "(this Map)"
816 * will appear in its place.
817 */
818 @Override
819 public String toString() {
820 if (isEmpty()) {
821 return "{}";
822 }
823
824 StringBuilder buffer = new StringBuilder(mSize * 28);
825 buffer.append('{');
826 for (int i=0; i<mSize; i++) {
827 if (i > 0) {
828 buffer.append(", ");
829 }
830 Object key = keyAt(i);
831 if (key != this) {
832 buffer.append(key);
833 } else {
834 buffer.append("(this Map)");
835 }
836 buffer.append('=');
837 Object value = valueAt(i);
838 if (value != this) {
Jeff Sharkeybffd2502019-02-28 16:39:12 -0700839 buffer.append(ArrayUtils.deepToString(value));
Dianne Hackborna17c0f52013-06-20 18:59:11 -0700840 } else {
841 buffer.append("(this Map)");
842 }
843 }
844 buffer.append('}');
845 return buffer.toString();
846 }
847
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700848 // ------------------------------------------------------------------------
849 // Interop with traditional Java containers. Not as efficient as using
850 // specialized collection APIs.
851 // ------------------------------------------------------------------------
852
853 private MapCollections<K, V> getCollection() {
854 if (mCollections == null) {
855 mCollections = new MapCollections<K, V>() {
856 @Override
857 protected int colGetSize() {
858 return mSize;
859 }
860
861 @Override
862 protected Object colGetEntry(int index, int offset) {
863 return mArray[(index<<1) + offset];
864 }
865
866 @Override
867 protected int colIndexOfKey(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700868 return indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700869 }
870
871 @Override
872 protected int colIndexOfValue(Object value) {
873 return indexOfValue(value);
874 }
875
876 @Override
877 protected Map<K, V> colGetMap() {
878 return ArrayMap.this;
879 }
880
881 @Override
882 protected void colPut(K key, V value) {
883 put(key, value);
884 }
885
886 @Override
887 protected V colSetValue(int index, V value) {
888 return setValueAt(index, value);
889 }
890
891 @Override
892 protected void colRemoveAt(int index) {
893 removeAt(index);
894 }
895
896 @Override
897 protected void colClear() {
898 clear();
899 }
900 };
901 }
902 return mCollections;
903 }
904
905 /**
906 * Determine if the array map contains all of the keys in the given collection.
907 * @param collection The collection whose contents are to be checked against.
908 * @return Returns true if this array map contains a key for every entry
909 * in <var>collection</var>, else returns false.
910 */
911 public boolean containsAll(Collection<?> collection) {
912 return MapCollections.containsAllHelper(this, collection);
913 }
914
915 /**
916 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
917 * @param map The map whose contents are to be retrieved.
918 */
919 @Override
920 public void putAll(Map<? extends K, ? extends V> map) {
921 ensureCapacity(mSize + map.size());
922 for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
923 put(entry.getKey(), entry.getValue());
924 }
925 }
926
927 /**
928 * Remove all keys in the array map that exist in the given collection.
929 * @param collection The collection whose contents are to be used to remove keys.
930 * @return Returns true if any keys were removed from the array map, else false.
931 */
932 public boolean removeAll(Collection<?> collection) {
933 return MapCollections.removeAllHelper(this, collection);
934 }
935
936 /**
937 * Remove all keys in the array map that do <b>not</b> exist in the given collection.
938 * @param collection The collection whose contents are to be used to determine which
939 * keys to keep.
940 * @return Returns true if any keys were removed from the array map, else false.
941 */
942 public boolean retainAll(Collection<?> collection) {
943 return MapCollections.retainAllHelper(this, collection);
944 }
945
946 /**
947 * Return a {@link java.util.Set} for iterating over and interacting with all mappings
948 * in the array map.
949 *
950 * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700951 * requires generating a number of temporary objects and allocates additional state
952 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700953 *
954 * <p><b>Note:</b></p> the semantics of this
955 * Set are subtly different than that of a {@link java.util.HashMap}: most important,
956 * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
957 * object that exists for the entire iterator, so you can <b>not</b> hold on to it
958 * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
959 */
960 @Override
961 public Set<Map.Entry<K, V>> entrySet() {
962 return getCollection().getEntrySet();
963 }
964
965 /**
966 * Return a {@link java.util.Set} for iterating over and interacting with all keys
967 * in the array map.
968 *
Chet Haasef4130cf2013-06-06 16:34:33 -0700969 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700970 * requires generating a number of temporary objects and allocates additional state
971 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700972 */
973 @Override
974 public Set<K> keySet() {
975 return getCollection().getKeySet();
976 }
977
978 /**
979 * Return a {@link java.util.Collection} for iterating over and interacting with all values
980 * in the array map.
981 *
Chet Haasef4130cf2013-06-06 16:34:33 -0700982 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700983 * requires generating a number of temporary objects and allocates additional state
984 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700985 */
986 @Override
987 public Collection<V> values() {
988 return getCollection().getValues();
989 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700990}