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