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