blob: 294a1799a2d38176db90a72216f7132618d89e32 [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
Adam Lesinski776abc22014-03-07 11:30:59 -050019import libcore.util.EmptyArray;
20
Mathew Inwood4eb56ab2018-08-14 17:24:32 +010021import android.annotation.UnsupportedAppUsage;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070022import java.util.Collection;
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -040023import java.util.ConcurrentModificationException;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070024import java.util.Map;
25import java.util.Set;
26
27/**
28 * ArrayMap is a generic key->value mapping data structure that is
29 * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
30 * It keeps its mappings in an array data structure -- an integer array of hash
31 * codes for each item, and an Object array of the key/value pairs. This allows it to
32 * avoid having to create an extra object for every entry put in to the map, and it
33 * also tries to control the growth of the size of these arrays more aggressively
34 * (since growing them only requires copying the entries in the array, not rebuilding
35 * a hash map).
36 *
37 * <p>Note that this implementation is not intended to be appropriate for data structures
38 * that may contain large numbers of items. It is generally slower than a traditional
39 * HashMap, since lookups require a binary search and adds and removes require inserting
40 * and deleting entries in the array. For containers holding up to hundreds of items,
Dianne Hackbornb993f412013-07-12 17:46:45 -070041 * the performance difference is not significant, less than 50%.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070042 *
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070043 * <p>Because this container is intended to better balance memory use, unlike most other
44 * standard Java containers it will shrink its array as items are removed from it. Currently
45 * you have no control over this shrinking -- if you set a capacity and then remove an
46 * item, it may reduce the capacity to better match the current size. In the future an
Dianne Hackbornb993f412013-07-12 17:46:45 -070047 * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070048 */
49public final class ArrayMap<K, V> implements Map<K, V> {
50 private static final boolean DEBUG = false;
51 private static final String TAG = "ArrayMap";
52
53 /**
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -040054 * Attempt to spot concurrent modifications to this data structure.
55 *
56 * It's best-effort, but any time we can throw something more diagnostic than an
57 * ArrayIndexOutOfBoundsException deep in the ArrayMap internals it's going to
58 * save a lot of development time.
59 *
60 * Good times to look for CME include after any allocArrays() call and at the end of
61 * functions that change mSize (put/remove/clear).
62 */
63 private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true;
64
65 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070066 * The minimum amount by which the capacity of a ArrayMap will increase.
67 * This is tuned to be relatively space-efficient.
68 */
69 private static final int BASE_SIZE = 4;
70
71 /**
72 * Maximum number of entries to have in array caches.
73 */
Jake Whartona8a04352018-09-29 01:52:24 -040074 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070075 private static final int CACHE_SIZE = 10;
76
77 /**
Dianne Hackborn894ce602015-12-04 13:25:48 -080078 * Special hash array value that indicates the container is immutable.
79 */
Jake Whartona8a04352018-09-29 01:52:24 -040080 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackborn894ce602015-12-04 13:25:48 -080081 static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
82
83 /**
Dianne Hackbornb87655b2013-07-17 19:06:22 -070084 * @hide Special immutable empty ArrayMap.
85 */
Jake Whartona8a04352018-09-29 01:52:24 -040086 @UnsupportedAppUsage(maxTargetSdk = 28) // Use your own singleton empty map.
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070087 public static final ArrayMap EMPTY = new ArrayMap<>(-1);
Dianne Hackbornb87655b2013-07-17 19:06:22 -070088
89 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070090 * Caches of small array objects to avoid spamming garbage. The cache
91 * Object[] variable is a pointer to a linked list of array objects.
92 * The first entry in the array is a pointer to the next array in the
93 * list; the second entry is a pointer to the int[] hash code array for it.
94 */
Jake Whartona8a04352018-09-29 01:52:24 -040095 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070096 static Object[] mBaseCache;
Jake Whartona8a04352018-09-29 01:52:24 -040097 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070098 static int mBaseCacheSize;
Jake Whartona8a04352018-09-29 01:52:24 -040099 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700100 static Object[] mTwiceBaseCache;
Jake Whartona8a04352018-09-29 01:52:24 -0400101 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700102 static int mTwiceBaseCacheSize;
103
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700104 final boolean mIdentityHashCode;
Jake Whartona8a04352018-09-29 01:52:24 -0400105 @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700106 int[] mHashes;
Jake Whartona8a04352018-09-29 01:52:24 -0400107 @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700108 Object[] mArray;
Jake Whartona8a04352018-09-29 01:52:24 -0400109 @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700110 int mSize;
111 MapCollections<K, V> mCollections;
112
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400113 private static int binarySearchHashes(int[] hashes, int N, int hash) {
114 try {
115 return ContainerHelpers.binarySearch(hashes, N, hash);
116 } catch (ArrayIndexOutOfBoundsException e) {
117 if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
118 throw new ConcurrentModificationException();
119 } else {
120 throw e; // the cache is poisoned at this point, there's not much we can do
121 }
122 }
123 }
124
Jake Whartona8a04352018-09-29 01:52:24 -0400125 @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700126 int indexOf(Object key, int hash) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700127 final int N = mSize;
128
129 // Important fast case: if nothing is in here, nothing to look for.
130 if (N == 0) {
131 return ~0;
132 }
133
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400134 int index = binarySearchHashes(mHashes, N, hash);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700135
136 // If the hash code wasn't found, then we have no entry for this key.
137 if (index < 0) {
138 return index;
139 }
140
141 // If the key at the returned index matches, that's what we want.
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700142 if (key.equals(mArray[index<<1])) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700143 return index;
144 }
145
146 // Search for a matching key after the index.
147 int end;
148 for (end = index + 1; end < N && mHashes[end] == hash; end++) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700149 if (key.equals(mArray[end << 1])) return end;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700150 }
151
152 // Search for a matching key before the index.
153 for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700154 if (key.equals(mArray[i << 1])) return i;
155 }
156
157 // Key not found -- return negative value indicating where a
158 // new entry for this key should go. We use the end of the
159 // hash chain to reduce the number of array entries that will
160 // need to be copied when inserting.
161 return ~end;
162 }
163
Jake Whartona8a04352018-09-29 01:52:24 -0400164 @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700165 int indexOfNull() {
166 final int N = mSize;
167
168 // Important fast case: if nothing is in here, nothing to look for.
169 if (N == 0) {
170 return ~0;
171 }
172
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400173 int index = binarySearchHashes(mHashes, N, 0);
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700174
175 // If the hash code wasn't found, then we have no entry for this key.
176 if (index < 0) {
177 return index;
178 }
179
180 // If the key at the returned index matches, that's what we want.
181 if (null == mArray[index<<1]) {
182 return index;
183 }
184
185 // Search for a matching key after the index.
186 int end;
187 for (end = index + 1; end < N && mHashes[end] == 0; end++) {
188 if (null == mArray[end << 1]) return end;
189 }
190
191 // Search for a matching key before the index.
192 for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
193 if (null == mArray[i << 1]) return i;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700194 }
195
196 // Key not found -- return negative value indicating where a
197 // new entry for this key should go. We use the end of the
198 // hash chain to reduce the number of array entries that will
199 // need to be copied when inserting.
200 return ~end;
201 }
202
Jake Whartona8a04352018-09-29 01:52:24 -0400203 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700204 private void allocArrays(final int size) {
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700205 if (mHashes == EMPTY_IMMUTABLE_INTS) {
206 throw new UnsupportedOperationException("ArrayMap is immutable");
207 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700208 if (size == (BASE_SIZE*2)) {
209 synchronized (ArrayMap.class) {
210 if (mTwiceBaseCache != null) {
211 final Object[] array = mTwiceBaseCache;
212 mArray = array;
213 mTwiceBaseCache = (Object[])array[0];
214 mHashes = (int[])array[1];
215 array[0] = array[1] = null;
216 mTwiceBaseCacheSize--;
217 if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
218 + " now have " + mTwiceBaseCacheSize + " entries");
219 return;
220 }
221 }
222 } else if (size == BASE_SIZE) {
223 synchronized (ArrayMap.class) {
224 if (mBaseCache != null) {
225 final Object[] array = mBaseCache;
226 mArray = array;
227 mBaseCache = (Object[])array[0];
228 mHashes = (int[])array[1];
229 array[0] = array[1] = null;
230 mBaseCacheSize--;
231 if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
232 + " now have " + mBaseCacheSize + " entries");
233 return;
234 }
235 }
236 }
237
238 mHashes = new int[size];
239 mArray = new Object[size<<1];
240 }
241
Jake Whartona8a04352018-09-29 01:52:24 -0400242 @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700243 private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
244 if (hashes.length == (BASE_SIZE*2)) {
245 synchronized (ArrayMap.class) {
246 if (mTwiceBaseCacheSize < CACHE_SIZE) {
247 array[0] = mTwiceBaseCache;
248 array[1] = hashes;
249 for (int i=(size<<1)-1; i>=2; i--) {
250 array[i] = null;
251 }
252 mTwiceBaseCache = array;
253 mTwiceBaseCacheSize++;
254 if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
255 + " now have " + mTwiceBaseCacheSize + " entries");
256 }
257 }
258 } else if (hashes.length == BASE_SIZE) {
259 synchronized (ArrayMap.class) {
260 if (mBaseCacheSize < CACHE_SIZE) {
261 array[0] = mBaseCache;
262 array[1] = hashes;
263 for (int i=(size<<1)-1; i>=2; i--) {
264 array[i] = null;
265 }
266 mBaseCache = array;
267 mBaseCacheSize++;
268 if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
269 + " now have " + mBaseCacheSize + " entries");
270 }
271 }
272 }
273 }
274
275 /**
276 * Create a new empty ArrayMap. The default capacity of an array map is 0, and
277 * will grow once items are added to it.
278 */
279 public ArrayMap() {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700280 this(0, false);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700281 }
282
283 /**
284 * Create a new ArrayMap with a given initial capacity.
285 */
286 public ArrayMap(int capacity) {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700287 this(capacity, false);
288 }
289
290 /** {@hide} */
291 public ArrayMap(int capacity, boolean identityHashCode) {
292 mIdentityHashCode = identityHashCode;
293
294 // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
295 // instance instead of the usual EmptyArray.INT. The reference
296 // is checked later to see if the array is allowed to grow.
297 if (capacity < 0) {
298 mHashes = EMPTY_IMMUTABLE_INTS;
299 mArray = EmptyArray.OBJECT;
300 } else if (capacity == 0) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500301 mHashes = EmptyArray.INT;
302 mArray = EmptyArray.OBJECT;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700303 } else {
304 allocArrays(capacity);
305 }
306 mSize = 0;
307 }
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700308
Chet Haase08735182013-06-04 10:44:40 -0700309 /**
310 * Create a new ArrayMap with the mappings from the given ArrayMap.
311 */
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700312 public ArrayMap(ArrayMap<K, V> map) {
Chet Haase08735182013-06-04 10:44:40 -0700313 this();
314 if (map != null) {
315 putAll(map);
316 }
317 }
318
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700319 /**
320 * Make the array map empty. All storage is released.
321 */
322 @Override
323 public void clear() {
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700324 if (mSize > 0) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400325 final int[] ohashes = mHashes;
326 final Object[] oarray = mArray;
327 final int osize = mSize;
Adam Lesinski776abc22014-03-07 11:30:59 -0500328 mHashes = EmptyArray.INT;
329 mArray = EmptyArray.OBJECT;
Dianne Hackborn390517b2013-05-30 15:03:32 -0700330 mSize = 0;
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400331 freeArrays(ohashes, oarray, osize);
332 }
333 if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize > 0) {
334 throw new ConcurrentModificationException();
Dianne Hackborn390517b2013-05-30 15:03:32 -0700335 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700336 }
337
338 /**
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700339 * @hide
340 * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
341 */
342 public void erase() {
343 if (mSize > 0) {
344 final int N = mSize<<1;
345 final Object[] array = mArray;
346 for (int i=0; i<N; i++) {
347 array[i] = null;
348 }
Dianne Hackborn4a7d8242013-10-03 10:19:20 -0700349 mSize = 0;
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700350 }
351 }
352
353 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700354 * Ensure the array map can hold at least <var>minimumCapacity</var>
355 * items.
356 */
357 public void ensureCapacity(int minimumCapacity) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400358 final int osize = mSize;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700359 if (mHashes.length < minimumCapacity) {
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700360 final int[] ohashes = mHashes;
361 final Object[] oarray = mArray;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700362 allocArrays(minimumCapacity);
Dianne Hackborn390517b2013-05-30 15:03:32 -0700363 if (mSize > 0) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400364 System.arraycopy(ohashes, 0, mHashes, 0, osize);
365 System.arraycopy(oarray, 0, mArray, 0, osize<<1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700366 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400367 freeArrays(ohashes, oarray, osize);
368 }
369 if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
370 throw new ConcurrentModificationException();
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700371 }
372 }
373
374 /**
375 * Check whether a key exists in the array.
376 *
377 * @param key The key to search for.
378 * @return Returns true if the key exists, else false.
379 */
380 @Override
381 public boolean containsKey(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700382 return indexOfKey(key) >= 0;
383 }
384
385 /**
386 * Returns the index of a key in the set.
387 *
388 * @param key The key to search for.
389 * @return Returns the index of the key if it exists, else a negative integer.
390 */
391 public int indexOfKey(Object key) {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700392 return key == null ? indexOfNull()
393 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700394 }
395
Jake Whartona8a04352018-09-29 01:52:24 -0400396 /**
397 * Returns an index for which {@link #valueAt} would return the
398 * specified value, or a negative number if no keys map to the
399 * specified value.
400 * Beware that this is a linear search, unlike lookups by key,
401 * and that multiple keys can map to the same value and this will
402 * find only one of them.
403 */
404 public int indexOfValue(Object value) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700405 final int N = mSize*2;
406 final Object[] array = mArray;
407 if (value == null) {
408 for (int i=1; i<N; i+=2) {
409 if (array[i] == null) {
410 return i>>1;
411 }
412 }
413 } else {
414 for (int i=1; i<N; i+=2) {
415 if (value.equals(array[i])) {
416 return i>>1;
417 }
418 }
419 }
420 return -1;
421 }
422
423 /**
424 * Check whether a value exists in the array. This requires a linear search
425 * through the entire array.
426 *
427 * @param value The value to search for.
428 * @return Returns true if the value exists, else false.
429 */
430 @Override
431 public boolean containsValue(Object value) {
432 return indexOfValue(value) >= 0;
433 }
434
435 /**
436 * Retrieve a value from the array.
437 * @param key The key of the value to retrieve.
438 * @return Returns the value associated with the given key,
439 * or null if there is no such key.
440 */
441 @Override
442 public V get(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700443 final int index = indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700444 return index >= 0 ? (V)mArray[(index<<1)+1] : null;
445 }
446
447 /**
448 * Return the key at the given index in the array.
449 * @param index The desired index, must be between 0 and {@link #size()}-1.
450 * @return Returns the key stored at the given index.
451 */
452 public K keyAt(int index) {
453 return (K)mArray[index << 1];
454 }
455
456 /**
457 * Return the value at the given index in the array.
458 * @param index The desired index, must be between 0 and {@link #size()}-1.
459 * @return Returns the value stored at the given index.
460 */
461 public V valueAt(int index) {
462 return (V)mArray[(index << 1) + 1];
463 }
464
465 /**
466 * Set the value at a given index in the array.
467 * @param index The desired index, must be between 0 and {@link #size()}-1.
468 * @param value The new value to store at this index.
469 * @return Returns the previous value at the given index.
470 */
471 public V setValueAt(int index, V value) {
472 index = (index << 1) + 1;
473 V old = (V)mArray[index];
474 mArray[index] = value;
475 return old;
476 }
477
478 /**
479 * Return true if the array map contains no items.
480 */
481 @Override
482 public boolean isEmpty() {
483 return mSize <= 0;
484 }
485
486 /**
487 * Add a new value to the array map.
Dianne Hackborna3fb40d2014-08-12 15:06:50 -0700488 * @param key The key under which to store the value. If
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700489 * this key already exists in the array, its value will be replaced.
490 * @param value The value to store for the given key.
491 * @return Returns the old value that was stored for the given key, or null if there
492 * was no such key.
493 */
494 @Override
495 public V put(K key, V value) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400496 final int osize = mSize;
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700497 final int hash;
498 int index;
499 if (key == null) {
500 hash = 0;
501 index = indexOfNull();
502 } else {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700503 hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700504 index = indexOf(key, hash);
505 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700506 if (index >= 0) {
507 index = (index<<1) + 1;
508 final V old = (V)mArray[index];
509 mArray[index] = value;
510 return old;
511 }
512
513 index = ~index;
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400514 if (osize >= mHashes.length) {
515 final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
516 : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700517
518 if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
519
520 final int[] ohashes = mHashes;
521 final Object[] oarray = mArray;
522 allocArrays(n);
523
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400524 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
525 throw new ConcurrentModificationException();
526 }
527
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700528 if (mHashes.length > 0) {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400529 if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700530 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
531 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
532 }
533
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400534 freeArrays(ohashes, oarray, osize);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700535 }
536
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400537 if (index < osize) {
538 if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700539 + " to " + (index+1));
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400540 System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700541 System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
542 }
543
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400544 if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
545 if (osize != mSize || index >= mHashes.length) {
546 throw new ConcurrentModificationException();
547 }
548 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700549 mHashes[index] = hash;
550 mArray[index<<1] = key;
551 mArray[(index<<1)+1] = value;
552 mSize++;
553 return null;
554 }
555
556 /**
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700557 * Special fast path for appending items to the end of the array without validation.
558 * The array must already be large enough to contain the item.
559 * @hide
560 */
Jake Whartona8a04352018-09-29 01:52:24 -0400561 @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use put(K, V).
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700562 public void append(K key, V value) {
563 int index = mSize;
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700564 final int hash = key == null ? 0
565 : (mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700566 if (index >= mHashes.length) {
567 throw new IllegalStateException("Array is full");
568 }
569 if (index > 0 && mHashes[index-1] > hash) {
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700570 RuntimeException e = new RuntimeException("here");
571 e.fillInStackTrace();
572 Log.w(TAG, "New hash " + hash
573 + " is before end of array hash " + mHashes[index-1]
574 + " at index " + index + " key " + key, e);
575 put(key, value);
576 return;
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700577 }
578 mSize = index+1;
579 mHashes[index] = hash;
580 index <<= 1;
581 mArray[index] = key;
582 mArray[index+1] = value;
583 }
584
585 /**
Dianne Hackborn9c3e74f2014-08-13 15:39:50 -0700586 * The use of the {@link #append} function can result in invalid array maps, in particular
587 * an array map where the same key appears multiple times. This function verifies that
588 * the array map is valid, throwing IllegalArgumentException if a problem is found. The
589 * main use for this method is validating an array map after unpacking from an IPC, to
590 * protect against malicious callers.
591 * @hide
592 */
593 public void validate() {
594 final int N = mSize;
595 if (N <= 1) {
596 // There can't be dups.
597 return;
598 }
599 int basehash = mHashes[0];
600 int basei = 0;
601 for (int i=1; i<N; i++) {
602 int hash = mHashes[i];
603 if (hash != basehash) {
604 basehash = hash;
605 basei = i;
606 continue;
607 }
608 // We are in a run of entries with the same hash code. Go backwards through
609 // the array to see if any keys are the same.
610 final Object cur = mArray[i<<1];
611 for (int j=i-1; j>=basei; j--) {
612 final Object prev = mArray[j<<1];
613 if (cur == prev) {
614 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
615 }
616 if (cur != null && prev != null && cur.equals(prev)) {
617 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
618 }
619 }
620 }
621 }
622
623 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700624 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
625 * @param array The array whose contents are to be retrieved.
626 */
627 public void putAll(ArrayMap<? extends K, ? extends V> array) {
628 final int N = array.mSize;
629 ensureCapacity(mSize + N);
Chet Haasef4130cf2013-06-06 16:34:33 -0700630 if (mSize == 0) {
631 if (N > 0) {
632 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
633 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
634 mSize = N;
635 }
636 } else {
637 for (int i=0; i<N; i++) {
638 put(array.keyAt(i), array.valueAt(i));
639 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700640 }
641 }
642
643 /**
644 * Remove an existing key from the array map.
645 * @param key The key of the mapping to remove.
646 * @return Returns the value that was stored under the key, or null if there
647 * was no such key.
648 */
649 @Override
650 public V remove(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700651 final int index = indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700652 if (index >= 0) {
653 return removeAt(index);
654 }
655
656 return null;
657 }
658
659 /**
660 * Remove the key/value mapping at the given index.
661 * @param index The desired index, must be between 0 and {@link #size()}-1.
662 * @return Returns the value that was stored at this index.
663 */
664 public V removeAt(int index) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700665 final Object old = mArray[(index << 1) + 1];
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400666 final int osize = mSize;
667 final int nsize;
668 if (osize <= 1) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700669 // Now empty.
670 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
Suprabh Shukla1938e342018-05-14 14:22:11 -0700671 final int[] ohashes = mHashes;
672 final Object[] oarray = mArray;
Adam Lesinski776abc22014-03-07 11:30:59 -0500673 mHashes = EmptyArray.INT;
674 mArray = EmptyArray.OBJECT;
Suprabh Shukla1938e342018-05-14 14:22:11 -0700675 freeArrays(ohashes, oarray, osize);
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400676 nsize = 0;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700677 } else {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400678 nsize = osize - 1;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700679 if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
680 // Shrunk enough to reduce size of arrays. We don't allow it to
681 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
682 // that and BASE_SIZE.
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400683 final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700684
685 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
686
687 final int[] ohashes = mHashes;
688 final Object[] oarray = mArray;
689 allocArrays(n);
690
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400691 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
692 throw new ConcurrentModificationException();
693 }
694
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700695 if (index > 0) {
696 if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
697 System.arraycopy(ohashes, 0, mHashes, 0, index);
698 System.arraycopy(oarray, 0, mArray, 0, index << 1);
699 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400700 if (index < nsize) {
701 if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700702 + " to " + index);
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400703 System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700704 System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400705 (nsize - index) << 1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700706 }
707 } else {
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400708 if (index < nsize) {
709 if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700710 + " to " + index);
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400711 System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700712 System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400713 (nsize - index) << 1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700714 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400715 mArray[nsize << 1] = null;
716 mArray[(nsize << 1) + 1] = null;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700717 }
718 }
Dan Sandlerd0ecb1e2017-04-13 20:21:12 -0400719 if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
720 throw new ConcurrentModificationException();
721 }
722 mSize = nsize;
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700723 return (V)old;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700724 }
725
726 /**
727 * Return the number of items in this array map.
728 */
729 @Override
730 public int size() {
731 return mSize;
732 }
733
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700734 /**
735 * {@inheritDoc}
736 *
737 * <p>This implementation returns false if the object is not a map, or
738 * if the maps have different sizes. Otherwise, for each key in this map,
739 * values of both maps are compared. If the values for any key are not
740 * equal, the method returns false, otherwise it returns true.
741 */
742 @Override
743 public boolean equals(Object object) {
744 if (this == object) {
745 return true;
746 }
747 if (object instanceof Map) {
748 Map<?, ?> map = (Map<?, ?>) object;
749 if (size() != map.size()) {
750 return false;
751 }
752
753 try {
754 for (int i=0; i<mSize; i++) {
755 K key = keyAt(i);
756 V mine = valueAt(i);
757 Object theirs = map.get(key);
758 if (mine == null) {
759 if (theirs != null || !map.containsKey(key)) {
760 return false;
761 }
762 } else if (!mine.equals(theirs)) {
763 return false;
764 }
765 }
766 } catch (NullPointerException ignored) {
767 return false;
768 } catch (ClassCastException ignored) {
769 return false;
770 }
771 return true;
772 }
773 return false;
774 }
775
776 /**
777 * {@inheritDoc}
778 */
779 @Override
780 public int hashCode() {
781 final int[] hashes = mHashes;
782 final Object[] array = mArray;
783 int result = 0;
784 for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
785 Object value = array[v];
786 result += hashes[i] ^ (value == null ? 0 : value.hashCode());
787 }
788 return result;
789 }
790
Dianne Hackborna17c0f52013-06-20 18:59:11 -0700791 /**
792 * {@inheritDoc}
793 *
794 * <p>This implementation composes a string by iterating over its mappings. If
795 * this map contains itself as a key or a value, the string "(this Map)"
796 * will appear in its place.
797 */
798 @Override
799 public String toString() {
800 if (isEmpty()) {
801 return "{}";
802 }
803
804 StringBuilder buffer = new StringBuilder(mSize * 28);
805 buffer.append('{');
806 for (int i=0; i<mSize; i++) {
807 if (i > 0) {
808 buffer.append(", ");
809 }
810 Object key = keyAt(i);
811 if (key != this) {
812 buffer.append(key);
813 } else {
814 buffer.append("(this Map)");
815 }
816 buffer.append('=');
817 Object value = valueAt(i);
818 if (value != this) {
819 buffer.append(value);
820 } else {
821 buffer.append("(this Map)");
822 }
823 }
824 buffer.append('}');
825 return buffer.toString();
826 }
827
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700828 // ------------------------------------------------------------------------
829 // Interop with traditional Java containers. Not as efficient as using
830 // specialized collection APIs.
831 // ------------------------------------------------------------------------
832
833 private MapCollections<K, V> getCollection() {
834 if (mCollections == null) {
835 mCollections = new MapCollections<K, V>() {
836 @Override
837 protected int colGetSize() {
838 return mSize;
839 }
840
841 @Override
842 protected Object colGetEntry(int index, int offset) {
843 return mArray[(index<<1) + offset];
844 }
845
846 @Override
847 protected int colIndexOfKey(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700848 return indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700849 }
850
851 @Override
852 protected int colIndexOfValue(Object value) {
853 return indexOfValue(value);
854 }
855
856 @Override
857 protected Map<K, V> colGetMap() {
858 return ArrayMap.this;
859 }
860
861 @Override
862 protected void colPut(K key, V value) {
863 put(key, value);
864 }
865
866 @Override
867 protected V colSetValue(int index, V value) {
868 return setValueAt(index, value);
869 }
870
871 @Override
872 protected void colRemoveAt(int index) {
873 removeAt(index);
874 }
875
876 @Override
877 protected void colClear() {
878 clear();
879 }
880 };
881 }
882 return mCollections;
883 }
884
885 /**
886 * Determine if the array map contains all of the keys in the given collection.
887 * @param collection The collection whose contents are to be checked against.
888 * @return Returns true if this array map contains a key for every entry
889 * in <var>collection</var>, else returns false.
890 */
891 public boolean containsAll(Collection<?> collection) {
892 return MapCollections.containsAllHelper(this, collection);
893 }
894
895 /**
896 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
897 * @param map The map whose contents are to be retrieved.
898 */
899 @Override
900 public void putAll(Map<? extends K, ? extends V> map) {
901 ensureCapacity(mSize + map.size());
902 for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
903 put(entry.getKey(), entry.getValue());
904 }
905 }
906
907 /**
908 * Remove all keys in the array map that exist in the given collection.
909 * @param collection The collection whose contents are to be used to remove keys.
910 * @return Returns true if any keys were removed from the array map, else false.
911 */
912 public boolean removeAll(Collection<?> collection) {
913 return MapCollections.removeAllHelper(this, collection);
914 }
915
916 /**
917 * Remove all keys in the array map that do <b>not</b> exist in the given collection.
918 * @param collection The collection whose contents are to be used to determine which
919 * keys to keep.
920 * @return Returns true if any keys were removed from the array map, else false.
921 */
922 public boolean retainAll(Collection<?> collection) {
923 return MapCollections.retainAllHelper(this, collection);
924 }
925
926 /**
927 * Return a {@link java.util.Set} for iterating over and interacting with all mappings
928 * in the array map.
929 *
930 * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700931 * requires generating a number of temporary objects and allocates additional state
932 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700933 *
934 * <p><b>Note:</b></p> the semantics of this
935 * Set are subtly different than that of a {@link java.util.HashMap}: most important,
936 * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
937 * object that exists for the entire iterator, so you can <b>not</b> hold on to it
938 * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
939 */
940 @Override
941 public Set<Map.Entry<K, V>> entrySet() {
942 return getCollection().getEntrySet();
943 }
944
945 /**
946 * Return a {@link java.util.Set} for iterating over and interacting with all keys
947 * in the array map.
948 *
Chet Haasef4130cf2013-06-06 16:34:33 -0700949 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700950 * requires generating a number of temporary objects and allocates additional state
951 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700952 */
953 @Override
954 public Set<K> keySet() {
955 return getCollection().getKeySet();
956 }
957
958 /**
959 * Return a {@link java.util.Collection} for iterating over and interacting with all values
960 * in the array map.
961 *
Chet Haasef4130cf2013-06-06 16:34:33 -0700962 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700963 * requires generating a number of temporary objects and allocates additional state
964 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700965 */
966 @Override
967 public Collection<V> values() {
968 return getCollection().getValues();
969 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700970}