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