1 /* 2 * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * The Universal Permissive License (UPL), Version 1.0 6 * 7 * Subject to the condition set forth below, permission is hereby granted to any 8 * person obtaining a copy of this software, associated documentation and/or 9 * data (collectively the "Software"), free of charge and under any and all 10 * copyright rights in the Software, and any and all patent rights owned or 11 * freely licensable by each licensor hereunder covering either (i) the 12 * unmodified Software as contributed to or provided by such licensor, or (ii) 13 * the Larger Works (as defined below), to deal in both 14 * 15 * (a) the Software, and 16 * 17 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if 18 * one is included with the Software each a "Larger Work" to which the Software 19 * is contributed by such licensors), 20 * 21 * without restriction, including without limitation the rights to copy, create 22 * derivative works of, display, perform, and distribute the Software and make, 23 * use, sell, offer for sale, import, export, have made, and have sold the 24 * Software and the Larger Work(s), and to sublicense the foregoing rights on 25 * either these or other terms. 26 * 27 * This license is subject to the following condition: 28 * 29 * The above copyright notice and either this complete permission notice or at a 30 * minimum a reference to the UPL must be included in all copies or substantial 31 * portions of the Software. 32 * 33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 39 * SOFTWARE. 40 */ 41 package jdk.internal.vm.compiler.collections; 42 43 import java.util.Iterator; 44 import java.util.Objects; 45 import java.util.function.BiFunction; 46 47 /** 48 * Implementation of a map with a memory-efficient structure that always preserves insertion order 49 * when iterating over keys. Particularly efficient when number of entries is 0 or smaller equal 50 * {@link #INITIAL_CAPACITY} or smaller 256. 51 * 52 * The key/value pairs are kept in an expanding flat object array with keys at even indices and 53 * values at odd indices. If the map has smaller or equal to {@link #HASH_THRESHOLD} entries, there 54 * is no additional hash data structure and comparisons are done via linear checking of the 55 * key/value pairs. For the case where the equality check is particularly cheap (e.g., just an 56 * object identity comparison), this limit below which the map is without an actual hash table is 57 * higher and configured at {@link #HASH_THRESHOLD_IDENTITY_COMPARE}. 58 * 59 * When the hash table needs to be constructed, the field {@link #hashArray} becomes a new hash 60 * array where an entry of 0 means no hit and otherwise denotes the entry number in the 61 * {@link #entries} array. The hash array is interpreted as an actual byte array if the indices fit 62 * within 8 bit, or as an array of short values if the indices fit within 16 bit, or as an array of 63 * integer values in other cases. 64 * 65 * Hash collisions are handled by chaining a linked list of {@link CollisionLink} objects that take 66 * the place of the values in the {@link #entries} array. 67 * 68 * Removing entries will put {@code null} into the {@link #entries} array. If the occupation of the 69 * map falls below a specific threshold, the map will be compressed via the 70 * {@link #maybeCompress(int)} method. 71 */ 72 final class EconomicMapImpl<K, V> implements EconomicMap<K, V>, EconomicSet<K> { 73 74 /** 75 * Initial number of key/value pair entries that is allocated in the first entries array. 76 */ 77 private static final int INITIAL_CAPACITY = 4; 78 79 /** 80 * Maximum number of entries that are moved linearly forward if a key is removed. 81 */ 82 private static final int COMPRESS_IMMEDIATE_CAPACITY = 8; 83 84 /** 85 * Minimum number of key/value pair entries added when the entries array is increased in size. 86 */ 87 private static final int MIN_CAPACITY_INCREASE = 8; 88 89 /** 90 * Number of entries above which a hash table is created. 91 */ 92 private static final int HASH_THRESHOLD = 4; 93 94 /** 95 * Number of entries above which a hash table is created when equality can be checked with 96 * object identity. 97 */ 98 private static final int HASH_THRESHOLD_IDENTITY_COMPARE = 8; 99 100 /** 101 * Maximum number of entries allowed in the map. 102 */ 103 private static final int MAX_ELEMENT_COUNT = Integer.MAX_VALUE >> 1; 104 105 /** 106 * Number of entries above which more than 1 byte is necessary for the hash index. 107 */ 108 private static final int LARGE_HASH_THRESHOLD = ((1 << Byte.SIZE) << 1); 109 110 /** 111 * Number of entries above which more than 2 bytes are are necessary for the hash index. 112 */ 113 private static final int VERY_LARGE_HASH_THRESHOLD = (LARGE_HASH_THRESHOLD << Byte.SIZE); 114 115 /** 116 * Total number of entries (actual entries plus deleted entries). 117 */ 118 private int totalEntries; 119 120 /** 121 * Number of deleted entries. 122 */ 123 private int deletedEntries; 124 125 /** 126 * Entries array with even indices storing keys and odd indices storing values. 127 */ 128 private Object[] entries; 129 130 /** 131 * Hash array that is interpreted either as byte or short or int array depending on number of 132 * map entries. 133 */ 134 private byte[] hashArray; 135 136 /** 137 * The strategy used for comparing keys or {@code null} for denoting special strategy 138 * {@link Equivalence#IDENTITY}. 139 */ 140 private final Equivalence strategy; 141 142 /** 143 * Intercept method for debugging purposes. 144 */ 145 private static <K, V> EconomicMapImpl<K, V> intercept(EconomicMapImpl<K, V> map) { 146 return map; 147 } 148 149 public static <K, V> EconomicMapImpl<K, V> create(Equivalence strategy, boolean isSet) { 150 return intercept(new EconomicMapImpl<>(strategy, isSet)); 151 } 152 153 public static <K, V> EconomicMapImpl<K, V> create(Equivalence strategy, int initialCapacity, boolean isSet) { 154 return intercept(new EconomicMapImpl<>(strategy, initialCapacity, isSet)); 155 } 156 157 public static <K, V> EconomicMapImpl<K, V> create(Equivalence strategy, UnmodifiableEconomicMap<K, V> other, boolean isSet) { 158 return intercept(new EconomicMapImpl<>(strategy, other, isSet)); 159 } 160 161 public static <K, V> EconomicMapImpl<K, V> create(Equivalence strategy, UnmodifiableEconomicSet<K> other, boolean isSet) { 162 return intercept(new EconomicMapImpl<>(strategy, other, isSet)); 163 } 164 165 private EconomicMapImpl(Equivalence strategy, boolean isSet) { 166 if (strategy == Equivalence.IDENTITY) { 167 this.strategy = null; 168 } else { 169 this.strategy = strategy; 170 } 171 this.isSet = isSet; 172 } 173 174 private EconomicMapImpl(Equivalence strategy, int initialCapacity, boolean isSet) { 175 this(strategy, isSet); 176 init(initialCapacity); 177 } 178 179 private EconomicMapImpl(Equivalence strategy, UnmodifiableEconomicMap<K, V> other, boolean isSet) { 180 this(strategy, isSet); 181 if (!initFrom(other)) { 182 init(other.size()); 183 putAll(other); 184 } 185 } 186 187 private EconomicMapImpl(Equivalence strategy, UnmodifiableEconomicSet<K> other, boolean isSet) { 188 this(strategy, isSet); 189 if (!initFrom(other)) { 190 init(other.size()); 191 addAll(other); 192 } 193 } 194 195 @SuppressWarnings("unchecked") 196 private boolean initFrom(Object o) { 197 if (o instanceof EconomicMapImpl) { 198 EconomicMapImpl<K, V> otherMap = (EconomicMapImpl<K, V>) o; 199 // We are only allowed to directly copy if the strategies of the two maps are the same. 200 if (strategy == otherMap.strategy) { 201 totalEntries = otherMap.totalEntries; 202 deletedEntries = otherMap.deletedEntries; 203 if (otherMap.entries != null) { 204 entries = otherMap.entries.clone(); 205 } 206 if (otherMap.hashArray != null) { 207 hashArray = otherMap.hashArray.clone(); 208 } 209 return true; 210 } 211 } 212 return false; 213 } 214 215 private void init(int size) { 216 if (size > INITIAL_CAPACITY) { 217 entries = new Object[size << 1]; 218 } 219 } 220 221 /** 222 * Links the collisions. Needs to be immutable class for allowing efficient shallow copy from 223 * other map on construction. 224 */ 225 private static final class CollisionLink { 226 227 CollisionLink(Object value, int next) { 228 this.value = value; 229 this.next = next; 230 } 231 232 final Object value; 233 234 /** 235 * Index plus one of the next entry in the collision link chain. 236 */ 237 final int next; 238 } 239 240 @SuppressWarnings("unchecked") 241 @Override 242 public V get(K key) { 243 Objects.requireNonNull(key); 244 245 int index = find(key); 246 if (index != -1) { 247 return (V) getValue(index); 248 } 249 return null; 250 } 251 252 private int find(K key) { 253 if (hasHashArray()) { 254 return findHash(key); 255 } else { 256 return findLinear(key); 257 } 258 } 259 260 private int findLinear(K key) { 261 for (int i = 0; i < totalEntries; i++) { 262 Object entryKey = entries[i << 1]; 263 if (entryKey != null && compareKeys(key, entryKey)) { 264 return i; 265 } 266 } 267 return -1; 268 } 269 270 private boolean compareKeys(Object key, Object entryKey) { 271 if (key == entryKey) { 272 return true; 273 } 274 if (strategy != null && strategy != Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE) { 275 if (strategy == Equivalence.DEFAULT) { 276 return key.equals(entryKey); 277 } else { 278 return strategy.equals(key, entryKey); 279 } 280 } 281 return false; 282 } 283 284 private int findHash(K key) { 285 int index = getHashArray(getHashIndex(key)) - 1; 286 if (index != -1) { 287 Object entryKey = getKey(index); 288 if (compareKeys(key, entryKey)) { 289 return index; 290 } else { 291 Object entryValue = getRawValue(index); 292 if (entryValue instanceof CollisionLink) { 293 return findWithCollision(key, (CollisionLink) entryValue); 294 } 295 } 296 } 297 298 return -1; 299 } 300 301 private int findWithCollision(K key, CollisionLink initialEntryValue) { 302 int index; 303 Object entryKey; 304 CollisionLink entryValue = initialEntryValue; 305 while (true) { 306 CollisionLink collisionLink = entryValue; 307 index = collisionLink.next; 308 entryKey = getKey(index); 309 if (compareKeys(key, entryKey)) { 310 return index; 311 } else { 312 Object value = getRawValue(index); 313 if (value instanceof CollisionLink) { 314 entryValue = (CollisionLink) getRawValue(index); 315 } else { 316 return -1; 317 } 318 } 319 } 320 } 321 322 private int getHashArray(int index) { 323 if (entries.length < LARGE_HASH_THRESHOLD) { 324 return (hashArray[index] & 0xFF); 325 } else if (entries.length < VERY_LARGE_HASH_THRESHOLD) { 326 int adjustedIndex = index << 1; 327 return (hashArray[adjustedIndex] & 0xFF) | ((hashArray[adjustedIndex + 1] & 0xFF) << 8); 328 } else { 329 int adjustedIndex = index << 2; 330 return (hashArray[adjustedIndex] & 0xFF) | ((hashArray[adjustedIndex + 1] & 0xFF) << 8) | ((hashArray[adjustedIndex + 2] & 0xFF) << 16) | ((hashArray[adjustedIndex + 3] & 0xFF) << 24); 331 } 332 } 333 334 private void setHashArray(int index, int value) { 335 if (entries.length < LARGE_HASH_THRESHOLD) { 336 hashArray[index] = (byte) value; 337 } else if (entries.length < VERY_LARGE_HASH_THRESHOLD) { 338 int adjustedIndex = index << 1; 339 hashArray[adjustedIndex] = (byte) value; 340 hashArray[adjustedIndex + 1] = (byte) (value >> 8); 341 } else { 342 int adjustedIndex = index << 2; 343 hashArray[adjustedIndex] = (byte) value; 344 hashArray[adjustedIndex + 1] = (byte) (value >> 8); 345 hashArray[adjustedIndex + 2] = (byte) (value >> 16); 346 hashArray[adjustedIndex + 3] = (byte) (value >> 24); 347 } 348 } 349 350 private int findAndRemoveHash(Object key) { 351 int hashIndex = getHashIndex(key); 352 int index = getHashArray(hashIndex) - 1; 353 if (index != -1) { 354 Object entryKey = getKey(index); 355 if (compareKeys(key, entryKey)) { 356 Object value = getRawValue(index); 357 int nextIndex = -1; 358 if (value instanceof CollisionLink) { 359 CollisionLink collisionLink = (CollisionLink) value; 360 nextIndex = collisionLink.next; 361 } 362 setHashArray(hashIndex, nextIndex + 1); 363 return index; 364 } else { 365 Object entryValue = getRawValue(index); 366 if (entryValue instanceof CollisionLink) { 367 return findAndRemoveWithCollision(key, (CollisionLink) entryValue, index); 368 } 369 } 370 } 371 372 return -1; 373 } 374 375 private int findAndRemoveWithCollision(Object key, CollisionLink initialEntryValue, int initialIndexValue) { 376 int index; 377 Object entryKey; 378 CollisionLink entryValue = initialEntryValue; 379 int lastIndex = initialIndexValue; 380 while (true) { 381 CollisionLink collisionLink = entryValue; 382 index = collisionLink.next; 383 entryKey = getKey(index); 384 if (compareKeys(key, entryKey)) { 385 Object value = getRawValue(index); 386 if (value instanceof CollisionLink) { 387 CollisionLink thisCollisionLink = (CollisionLink) value; 388 setRawValue(lastIndex, new CollisionLink(collisionLink.value, thisCollisionLink.next)); 389 } else { 390 setRawValue(lastIndex, collisionLink.value); 391 } 392 return index; 393 } else { 394 Object value = getRawValue(index); 395 if (value instanceof CollisionLink) { 396 entryValue = (CollisionLink) getRawValue(index); 397 lastIndex = index; 398 } else { 399 return -1; 400 } 401 } 402 } 403 } 404 405 private int getHashIndex(Object key) { 406 int hash; 407 if (strategy != null && strategy != Equivalence.DEFAULT) { 408 if (strategy == Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE) { 409 hash = System.identityHashCode(key); 410 } else { 411 hash = strategy.hashCode(key); 412 } 413 } else { 414 hash = key.hashCode(); 415 } 416 hash = hash ^ (hash >>> 16); 417 return hash & (getHashTableSize() - 1); 418 } 419 420 @SuppressWarnings("unchecked") 421 @Override 422 public V put(K key, V value) { 423 if (key == null) { 424 throw new UnsupportedOperationException("null not supported as key!"); 425 } 426 int index = find(key); 427 if (index != -1) { 428 Object oldValue = getValue(index); 429 setValue(index, value); 430 return (V) oldValue; 431 } 432 433 int nextEntryIndex = totalEntries; 434 if (entries == null) { 435 entries = new Object[INITIAL_CAPACITY << 1]; 436 } else if (entries.length == nextEntryIndex << 1) { 437 grow(); 438 439 assert entries.length > totalEntries << 1; 440 // Can change if grow is actually compressing. 441 nextEntryIndex = totalEntries; 442 } 443 444 setKey(nextEntryIndex, key); 445 setValue(nextEntryIndex, value); 446 totalEntries++; 447 448 if (hasHashArray()) { 449 // Rehash on collision if hash table is more than three quarters full. 450 boolean rehashOnCollision = (getHashTableSize() < (size() + (size() >> 1))); 451 putHashEntry(key, nextEntryIndex, rehashOnCollision); 452 } else if (totalEntries > getHashThreshold()) { 453 createHash(); 454 } 455 456 return null; 457 } 458 459 /** 460 * Number of entries above which a hash table should be constructed. 461 */ 462 private int getHashThreshold() { 463 if (strategy == null || strategy == Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE) { 464 return HASH_THRESHOLD_IDENTITY_COMPARE; 465 } else { 466 return HASH_THRESHOLD; 467 } 468 } 469 470 private void grow() { 471 int entriesLength = entries.length; 472 int newSize = (entriesLength >> 1) + Math.max(MIN_CAPACITY_INCREASE, entriesLength >> 2); 473 if (newSize > MAX_ELEMENT_COUNT) { 474 throw new UnsupportedOperationException("map grown too large!"); 475 } 476 Object[] newEntries = new Object[newSize << 1]; 477 System.arraycopy(entries, 0, newEntries, 0, entriesLength); 478 entries = newEntries; 479 if ((entriesLength < LARGE_HASH_THRESHOLD && newEntries.length >= LARGE_HASH_THRESHOLD) || 480 (entriesLength < VERY_LARGE_HASH_THRESHOLD && newEntries.length > VERY_LARGE_HASH_THRESHOLD)) { 481 // Rehash in order to change number of bits reserved for hash indices. 482 createHash(); 483 } 484 } 485 486 /** 487 * Compresses the graph if there is a large number of deleted entries and returns the translated 488 * new next index. 489 */ 490 private int maybeCompress(int nextIndex) { 491 if (entries.length != INITIAL_CAPACITY << 1 && deletedEntries >= (totalEntries >> 1) + (totalEntries >> 2)) { 492 return compressLarge(nextIndex); 493 } 494 return nextIndex; 495 } 496 497 /** 498 * Compresses the graph and returns the translated new next index. 499 */ 500 private int compressLarge(int nextIndex) { 501 int size = INITIAL_CAPACITY; 502 int remaining = totalEntries - deletedEntries; 503 504 while (size <= remaining) { 505 size += Math.max(MIN_CAPACITY_INCREASE, size >> 1); 506 } 507 508 Object[] newEntries = new Object[size << 1]; 509 int z = 0; 510 int newNextIndex = remaining; 511 for (int i = 0; i < totalEntries; ++i) { 512 Object key = getKey(i); 513 if (i == nextIndex) { 514 newNextIndex = z; 515 } 516 if (key != null) { 517 newEntries[z << 1] = key; 518 newEntries[(z << 1) + 1] = getValue(i); 519 z++; 520 } 521 } 522 523 this.entries = newEntries; 524 totalEntries = z; 525 deletedEntries = 0; 526 if (z <= getHashThreshold()) { 527 this.hashArray = null; 528 } else { 529 createHash(); 530 } 531 return newNextIndex; 532 } 533 534 private int getHashTableSize() { 535 if (entries.length < LARGE_HASH_THRESHOLD) { 536 return hashArray.length; 537 } else if (entries.length < VERY_LARGE_HASH_THRESHOLD) { 538 return hashArray.length >> 1; 539 } else { 540 return hashArray.length >> 2; 541 } 542 } 543 544 private void createHash() { 545 int entryCount = size(); 546 547 // Calculate smallest 2^n that is greater number of entries. 548 int size = getHashThreshold(); 549 while (size <= entryCount) { 550 size <<= 1; 551 } 552 553 // Give extra size to avoid collisions. 554 size <<= 1; 555 556 if (this.entries.length >= VERY_LARGE_HASH_THRESHOLD) { 557 // Every entry has 4 bytes. 558 size <<= 2; 559 } else if (this.entries.length >= LARGE_HASH_THRESHOLD) { 560 // Every entry has 2 bytes. 561 size <<= 1; 562 } else { 563 // Entries are very small => give extra size to further reduce collisions. 564 size <<= 1; 565 } 566 567 hashArray = new byte[size]; 568 for (int i = 0; i < totalEntries; i++) { 569 Object entryKey = getKey(i); 570 if (entryKey != null) { 571 putHashEntry(entryKey, i, false); 572 } 573 } 574 } 575 576 private void putHashEntry(Object key, int entryIndex, boolean rehashOnCollision) { 577 int hashIndex = getHashIndex(key); 578 int oldIndex = getHashArray(hashIndex) - 1; 579 if (oldIndex != -1 && rehashOnCollision) { 580 this.createHash(); 581 return; 582 } 583 setHashArray(hashIndex, entryIndex + 1); 584 Object value = getRawValue(entryIndex); 585 if (oldIndex != -1) { 586 assert entryIndex != oldIndex : "this cannot happend and would create an endless collision link cycle"; 587 if (value instanceof CollisionLink) { 588 CollisionLink collisionLink = (CollisionLink) value; 589 setRawValue(entryIndex, new CollisionLink(collisionLink.value, oldIndex)); 590 } else { 591 setRawValue(entryIndex, new CollisionLink(getRawValue(entryIndex), oldIndex)); 592 } 593 } else { 594 if (value instanceof CollisionLink) { 595 CollisionLink collisionLink = (CollisionLink) value; 596 setRawValue(entryIndex, collisionLink.value); 597 } 598 } 599 } 600 601 @Override 602 public int size() { 603 return totalEntries - deletedEntries; 604 } 605 606 @Override 607 public boolean containsKey(K key) { 608 return find(key) != -1; 609 } 610 611 @Override 612 public void clear() { 613 entries = null; 614 hashArray = null; 615 totalEntries = deletedEntries = 0; 616 } 617 618 private boolean hasHashArray() { 619 return hashArray != null; 620 } 621 622 @SuppressWarnings("unchecked") 623 @Override 624 public V removeKey(K key) { 625 if (key == null) { 626 throw new UnsupportedOperationException("null not supported as key!"); 627 } 628 int index; 629 if (hasHashArray()) { 630 index = this.findAndRemoveHash(key); 631 } else { 632 index = this.findLinear(key); 633 } 634 635 if (index != -1) { 636 Object value = getValue(index); 637 remove(index); 638 return (V) value; 639 } 640 return null; 641 } 642 643 /** 644 * Removes the element at the specific index and returns the index of the next element. This can 645 * be a different value if graph compression was triggered. 646 */ 647 private int remove(int indexToRemove) { 648 int index = indexToRemove; 649 int entriesAfterIndex = totalEntries - index - 1; 650 int result = index + 1; 651 652 // Without hash array, compress immediately. 653 if (entriesAfterIndex <= COMPRESS_IMMEDIATE_CAPACITY && !hasHashArray()) { 654 while (index < totalEntries - 1) { 655 setKey(index, getKey(index + 1)); 656 setRawValue(index, getRawValue(index + 1)); 657 index++; 658 } 659 result--; 660 } 661 662 setKey(index, null); 663 setRawValue(index, null); 664 if (index == totalEntries - 1) { 665 // Make sure last element is always non-null. 666 totalEntries--; 667 while (index > 0 && getKey(index - 1) == null) { 668 totalEntries--; 669 deletedEntries--; 670 index--; 671 } 672 } else { 673 deletedEntries++; 674 result = maybeCompress(result); 675 } 676 677 return result; 678 } 679 680 private abstract class SparseMapIterator<E> implements Iterator<E> { 681 682 protected int current; 683 684 @Override 685 public boolean hasNext() { 686 return current < totalEntries; 687 } 688 689 @Override 690 public void remove() { 691 if (hasHashArray()) { 692 EconomicMapImpl.this.findAndRemoveHash(getKey(current - 1)); 693 } 694 current = EconomicMapImpl.this.remove(current - 1); 695 } 696 } 697 698 @Override 699 public Iterable<V> getValues() { 700 return new Iterable<V>() { 701 @Override 702 public Iterator<V> iterator() { 703 return new SparseMapIterator<V>() { 704 @SuppressWarnings("unchecked") 705 @Override 706 public V next() { 707 Object result; 708 while (true) { 709 result = getValue(current); 710 if (result == null && getKey(current) == null) { 711 // values can be null, double-check if key is also null 712 current++; 713 } else { 714 current++; 715 break; 716 } 717 } 718 return (V) result; 719 } 720 }; 721 } 722 }; 723 } 724 725 @Override 726 public Iterable<K> getKeys() { 727 return this; 728 } 729 730 @Override 731 public boolean isEmpty() { 732 return this.size() == 0; 733 } 734 735 @Override 736 public MapCursor<K, V> getEntries() { 737 return new MapCursor<K, V>() { 738 int current = -1; 739 740 @Override 741 public boolean advance() { 742 current++; 743 if (current >= totalEntries) { 744 return false; 745 } else { 746 while (EconomicMapImpl.this.getKey(current) == null) { 747 // Skip over null entries 748 current++; 749 } 750 return true; 751 } 752 } 753 754 @SuppressWarnings("unchecked") 755 @Override 756 public K getKey() { 757 return (K) EconomicMapImpl.this.getKey(current); 758 } 759 760 @SuppressWarnings("unchecked") 761 @Override 762 public V getValue() { 763 return (V) EconomicMapImpl.this.getValue(current); 764 } 765 766 @Override 767 public void remove() { 768 if (hasHashArray()) { 769 EconomicMapImpl.this.findAndRemoveHash(EconomicMapImpl.this.getKey(current)); 770 } 771 current = EconomicMapImpl.this.remove(current) - 1; 772 } 773 }; 774 } 775 776 @SuppressWarnings("unchecked") 777 @Override 778 public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { 779 for (int i = 0; i < totalEntries; i++) { 780 Object entryKey = getKey(i); 781 if (entryKey != null) { 782 Object newValue = function.apply((K) entryKey, (V) getValue(i)); 783 setValue(i, newValue); 784 } 785 } 786 } 787 788 private Object getKey(int index) { 789 return entries[index << 1]; 790 } 791 792 private void setKey(int index, Object newValue) { 793 entries[index << 1] = newValue; 794 } 795 796 private void setValue(int index, Object newValue) { 797 Object oldValue = getRawValue(index); 798 if (oldValue instanceof CollisionLink) { 799 CollisionLink collisionLink = (CollisionLink) oldValue; 800 setRawValue(index, new CollisionLink(newValue, collisionLink.next)); 801 } else { 802 setRawValue(index, newValue); 803 } 804 } 805 806 private void setRawValue(int index, Object newValue) { 807 entries[(index << 1) + 1] = newValue; 808 } 809 810 private Object getRawValue(int index) { 811 return entries[(index << 1) + 1]; 812 } 813 814 private Object getValue(int index) { 815 Object object = getRawValue(index); 816 if (object instanceof CollisionLink) { 817 return ((CollisionLink) object).value; 818 } 819 return object; 820 } 821 822 private final boolean isSet; 823 824 @Override 825 public String toString() { 826 StringBuilder builder = new StringBuilder(); 827 builder.append(isSet ? "set(size=" : "map(size=").append(size()).append(", {"); 828 String sep = ""; 829 MapCursor<K, V> cursor = getEntries(); 830 while (cursor.advance()) { 831 builder.append(sep); 832 if (isSet) { 833 builder.append(cursor.getKey()); 834 } else { 835 builder.append("(").append(cursor.getKey()).append(",").append(cursor.getValue()).append(")"); 836 } 837 sep = ","; 838 } 839 builder.append("})"); 840 return builder.toString(); 841 } 842 843 @Override 844 public Iterator<K> iterator() { 845 return new SparseMapIterator<K>() { 846 @SuppressWarnings("unchecked") 847 @Override 848 public K next() { 849 Object result; 850 while ((result = getKey(current++)) == null) { 851 // skip null entries 852 } 853 return (K) result; 854 } 855 }; 856 } 857 858 @Override 859 public boolean contains(K element) { 860 return containsKey(element); 861 } 862 863 @SuppressWarnings("unchecked") 864 @Override 865 public boolean add(K element) { 866 return put(element, (V) element) == null; 867 } 868 869 @Override 870 public void remove(K element) { 871 removeKey(element); 872 } 873 }