View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
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    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.util;
16  
17  import java.io.IOException;
18  import java.io.ObjectInputStream;
19  import java.io.ObjectOutputStream;
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.NoSuchElementException;
28  import java.util.Objects;
29  import java.util.Set;
30  
31  /**
32   * Simple and efficient linked map or better ordered map implementation to
33   * replace the default linked list which is heavy.
34   *
35   * This map does not support null and it is not thread-safe. It implements the
36   * map interface but only for compatibility reason in the sense of replacing a
37   * regular map. Iterator and streaming methods are either not implemented or
38   * less efficient.
39   *
40   * It goes the extra mile to avoid the overhead of wrapper objects.
41   *
42   * Because you typically know what you do, we run minimal index checks only and
43   * rely on the default exceptions by Java. Why should we do things twice?
44   *
45   * Important Note: This is meant for small maps because to save on memory
46   * allocation and churn, we are not keeping a wrapper for a reference from the
47   * map to the list, only from the list to the map. Hence when you remove a key,
48   * we have to iterate the entire list. Mostly, half of it most likely, but still
49   * expensive enough. When you have something small like 10 to 20 entries, this
50   * won't matter that much especially when a remove might be a rare event.
51   *
52   * This is based on FashHashMap from XLT which is based on a version from:
53   * https://github.com/mikvor/hashmapTest/blob/master/src/main/java/map/objobj/ObjObjMap.java
54   * No concrete license specified at the source. The project is public domain.
55   *
56   * @param <K> the type of the key
57   * @param <V> the type of the value
58   *
59   * @author Ren&eacute; Schwietzke
60   */
61  public class OrderedFastHashMap<K, V> implements Map<K, V>, Serializable {
62      // our placeholders in the map
63      private static final Object FREE_KEY_ = null;
64      private static final Object REMOVED_KEY_ = new Object();
65  
66      // Fill factor, must be between (0 and 1)
67      private static final double FILLFACTOR_ = 0.7d;
68  
69      // The map with the key value pairs */
70      private Object[] mapData_;
71  
72      // We will resize a map once it reaches this size
73      private int mapThreshold_;
74  
75      // Current map size
76      private int mapSize_;
77  
78      // the list to impose order, the list refers to the key and value
79      // position in the map, hence needs an update every time the
80      // map sees an update (in regards to positions).
81      private int[] orderedList_;
82  
83      // the size of the orderedList, in case we proactivly sized
84      // it larger
85      private int orderedListSize_;
86  
87      /**
88       * Default constructor which create an ordered map with default size.
89       */
90      public OrderedFastHashMap() {
91          this(8);
92      }
93  
94      /**
95       * Custom constructor to get a map with a custom size and fill factor. We are
96       * not spending time on range checks, rather use a default if things are wrong.
97       *
98       * @param size the size to use, must 0 or positive, negative values default to 0
99       */
100     public OrderedFastHashMap(final int size) {
101         if (size > 0) {
102             final int capacity = arraySize(size, FILLFACTOR_);
103 
104             this.mapData_ = new Object[capacity << 1];
105             this.mapThreshold_ = (int) (capacity * FILLFACTOR_);
106 
107             this.orderedList_ = new int[capacity];
108         }
109         else {
110             this.mapData_ = new Object[0];
111             this.mapThreshold_ = 0;
112 
113             this.orderedList_ = new int[0];
114         }
115     }
116 
117     /**
118      * Get a value for a key, any key type is permitted due to
119      * the nature of the Map interface.
120      *
121      * @param key the key
122      * @return the value or null, if the key does not exist
123      */
124     @Override
125     public V get(final Object key) {
126         final int length = this.mapData_.length;
127 
128         // nothing in it
129         if (length == 0) {
130             return null;
131         }
132 
133         int ptr = (key.hashCode() & ((length >> 1) - 1)) << 1;
134         Object k = mapData_[ptr];
135 
136         if (k == FREE_KEY_) {
137             return null; // end of chain already
138         }
139 
140         // we checked FREE
141         if (k.hashCode() == key.hashCode() && k.equals(key)) {
142             return (V) this.mapData_[ptr + 1];
143         }
144 
145         // we have not found it, search longer
146         final int originalPtr = ptr;
147         while (true) {
148             ptr = (ptr + 2) & (length - 1); // that's next index
149 
150             // if we searched the entire array, we can stop
151             if (originalPtr == ptr) {
152                 return null;
153             }
154 
155             k = this.mapData_[ptr];
156 
157             if (k == FREE_KEY_) {
158                 return null;
159             }
160 
161             if (k != REMOVED_KEY_) {
162                 if (k.hashCode() == key.hashCode() && k.equals(key)) {
163                     return (V) this.mapData_[ptr + 1];
164                 }
165             }
166         }
167     }
168 
169     /**
170      * Adds a key and value to the internal position structure
171      *
172      * @param key the key
173      * @param value the value to store
174      * @param listPosition defines where to add the new key/value pair
175      *
176      * @return the old value or null if they key was not known before
177      */
178     private V put(final K key, final V value, final Position listPosition) {
179         if (mapSize_ >= mapThreshold_) {
180             rehash(this.mapData_.length == 0 ? 4 : this.mapData_.length << 1);
181         }
182 
183         int ptr = getStartIndex(key) << 1;
184         Object k = mapData_[ptr];
185 
186         if (k == FREE_KEY_) {
187             // end of chain already
188             mapData_[ptr] = key;
189             mapData_[ptr + 1] = value;
190 
191             // ok, remember position, it is a new entry
192             orderedListAdd(listPosition, ptr);
193 
194             mapSize_++;
195 
196             return null;
197         }
198         else if (k.equals(key)) {
199             // we check FREE and REMOVED prior to this call
200             final Object ret = mapData_[ptr + 1];
201             mapData_[ptr + 1] = value;
202 
203             /// existing entry, no need to update the position
204 
205             return (V) ret;
206         }
207 
208         int firstRemoved = -1;
209         if (k == REMOVED_KEY_) {
210             firstRemoved = ptr; // we may find a key later
211         }
212 
213         while (true) {
214             ptr = (ptr + 2) & (this.mapData_.length - 1); // that's next index calculation
215             k = mapData_[ptr];
216 
217             if (k == FREE_KEY_) {
218                 if (firstRemoved != -1) {
219                     ptr = firstRemoved;
220                 }
221                 mapData_[ptr] = key;
222                 mapData_[ptr + 1] = value;
223 
224                 // ok, remember position, it is a new entry
225                 orderedListAdd(listPosition, ptr);
226 
227                 mapSize_++;
228 
229                 return null;
230             }
231             else if (k.equals(key)) {
232                 final Object ret = mapData_[ptr + 1];
233                 mapData_[ptr + 1] = value;
234 
235                 // same key, different value, this does not change the order
236 
237                 return (V) ret;
238             }
239             else if (k == REMOVED_KEY_) {
240                 if (firstRemoved == -1) {
241                     firstRemoved = ptr;
242                 }
243             }
244         }
245     }
246 
247     /**
248      * Remove a key from the map. Returns the stored value or
249      * null of the key is not known.
250      *
251      * @param key the key to remove
252      * @return the stored value or null if the key does not exist
253      */
254     @Override
255     public V remove(final Object key) {
256         final int length = this.mapData_.length;
257         // it is empty
258         if (length == 0) {
259             return null;
260         }
261 
262         int ptr = getStartIndex(key) << 1;
263         Object k = this.mapData_[ptr];
264 
265         if (k == FREE_KEY_) {
266             return null; // end of chain already
267         }
268         else if (k.equals(key)) {
269             // we check FREE and REMOVED prior to this call
270             this.mapSize_--;
271 
272             if (this.mapData_[(ptr + 2) & (length - 1)] == FREE_KEY_) {
273                 this.mapData_[ptr] = FREE_KEY_;
274             }
275             else {
276                 this.mapData_[ptr] = REMOVED_KEY_;
277             }
278 
279             final V ret = (V) this.mapData_[ptr + 1];
280             this.mapData_[ptr + 1] = null;
281 
282             // take this out of the list
283             orderedListRemove(ptr);
284 
285             return ret;
286         }
287 
288         while (true) {
289             ptr = (ptr + 2) & (length - 1); // that's next index calculation
290             k = this.mapData_[ptr];
291 
292             if (k == FREE_KEY_) {
293                 return null;
294             }
295             else if (k.equals(key)) {
296                 this.mapSize_--;
297                 if (this.mapData_[(ptr + 2) & (length - 1)] == FREE_KEY_) {
298                     this.mapData_[ptr] = FREE_KEY_;
299                 }
300                 else {
301                     this.mapData_[ptr] = REMOVED_KEY_;
302                 }
303 
304                 final V ret = (V) this.mapData_[ptr + 1];
305                 this.mapData_[ptr + 1] = null;
306 
307                 // take this out of the list
308                 orderedListRemove(ptr);
309 
310                 return ret;
311             }
312         }
313     }
314 
315     /**
316      * Returns the size of the map, effectively the number of entries.
317      *
318      * @return the size of the map
319      */
320     @Override
321     public int size() {
322         return mapSize_;
323     }
324 
325     /**
326      * Rehash the map.
327      *
328      * @param newCapacity the new size of the map
329      */
330     private void rehash(final int newCapacity) {
331         this.mapThreshold_ = (int) (newCapacity / 2 * FILLFACTOR_);
332 
333         final Object[] oldData = this.mapData_;
334 
335         this.mapData_ = new Object[newCapacity];
336 
337         // we just have to grow it and not touch it at all after that,
338         // just use it as source for the new map via the old
339         final int[] oldOrderedList = this.orderedList_;
340         final int oldOrderedListSize = this.orderedListSize_;
341         this.orderedList_ = new int[newCapacity];
342 
343         this.mapSize_ = 0;
344         this.orderedListSize_ = 0;
345 
346         // we use our ordered list as source and the old
347         // array as reference
348         // we basically rebuild the map and the ordering
349         // from scratch
350         for (int i = 0; i < oldOrderedListSize; i++) {
351             final int pos = oldOrderedList[i];
352 
353             // get us the old data
354             final K key = (K) oldData[pos];
355             final V value = (V) oldData[pos + 1];
356 
357             // write the old to the new map without updating
358             // the positioning
359             put(key, value, Position.LAST);
360         }
361     }
362 
363     /**
364      * Returns a list of all keys in order of addition.
365      * This is an expensive operation, because we get a static
366      * list back that is not backed by the implementation. Changes
367      * to the returned list are not reflected in the map.
368      *
369      * @return a list of keys as inserted into the map
370      */
371     public List<K> keys() {
372         final List<K> result = new ArrayList<>(this.orderedListSize_);
373 
374         for (int i = 0; i < this.orderedListSize_; i++) {
375             final int pos = this.orderedList_[i];
376             final Object o = this.mapData_[pos];
377             result.add((K) o);
378         }
379 
380         return result;
381     }
382 
383     /**
384      * Returns a list of all values ordered by when the key was
385      * added. This is an expensive operation, because we get a static
386      * list back that is not backed by the implementation. Changes
387      * to the returned list are not reflected in the map.
388      *
389      * @return a list of values
390      */
391     @Override
392     public List<V> values() {
393         final List<V> result = new ArrayList<>(this.orderedListSize_);
394 
395         for (int i = 0; i < this.orderedListSize_; i++) {
396             final int pos = this.orderedList_[i];
397             final Object o = this.mapData_[pos + 1];
398             result.add((V) o);
399         }
400 
401         return result;
402     }
403 
404     /**
405      * Clears the map, reuses the data structure by clearing it out. It won't shrink
406      * the underlying arrays!
407      */
408     @Override
409     public void clear() {
410         this.mapSize_ = 0;
411         this.orderedListSize_ = 0;
412         Arrays.fill(this.mapData_, FREE_KEY_);
413         // Arrays.fill(this.orderedList, 0);
414     }
415 
416     /**
417      * Get us the start index from where we search or insert into the map
418      *
419      * @param key the key to calculate the position for
420      * @return the start position
421      */
422     private int getStartIndex(final Object key) {
423         // key is not null here
424         return key.hashCode() & ((this.mapData_.length >> 1) - 1);
425     }
426 
427     /**
428      * Return the least power of two greater than or equal to the specified value.
429      *
430      * <p>
431      * Note that this function will return 1 when the argument is 0.
432      *
433      * @param x a long integer smaller than or equal to 2<sup>62</sup>.
434      * @return the least power of two greater than or equal to the specified value.
435      */
436     private static long nextPowerOfTwo(final long x) {
437         if (x == 0) {
438             return 1;
439         }
440 
441         long r = x - 1;
442         r |= r >> 1;
443         r |= r >> 2;
444         r |= r >> 4;
445         r |= r >> 8;
446         r |= r >> 16;
447 
448         return (r | r >> 32) + 1;
449     }
450 
451     /**
452      * Returns the least power of two smaller than or equal to 2<sup>30</sup> and
453      * larger than or equal to <code>Math.ceil( expected / f )</code>.
454      *
455      * @param expected the expected number of elements in a hash table.
456      * @param f        the load factor.
457      * @return the minimum possible size for a backing array.
458      * @throws IllegalArgumentException if the necessary size is larger than
459      *                                  2<sup>30</sup>.
460      */
461     private static int arraySize(final int expected, final double f) {
462         final long s = Math.max(2, nextPowerOfTwo((long) Math.ceil(expected / f)));
463 
464         if (s > (1 << 30)) {
465             throw new IllegalArgumentException(
466                     "Too large (" + expected + " expected elements with load factor " + f + ")");
467         }
468         return (int) s;
469     }
470 
471     /**
472      * Returns an entry consisting of key and value at a given position.
473      * This position relates to the ordered key list that maintain the
474      * addition order for this map.
475      *
476      * @param index the position to fetch
477      * @return an entry of key and value
478      * @throws IndexOutOfBoundsException when the ask for the position is invalid
479      */
480     public Entry<K, V> getEntry(final int index) {
481         if (index < 0 || index >= this.orderedListSize_) {
482             throw new IndexOutOfBoundsException(String.format("Index: %s, Size: %s", index, this.orderedListSize_));
483         }
484 
485         final int pos = this.orderedList_[index];
486         return new Entry(this.mapData_[pos], this.mapData_[pos + 1]);
487     }
488 
489     /**
490      * Returns the key at a certain position of the ordered list that
491      * keeps the addition order of this map.
492      *
493      * @param index the position to fetch
494      * @return the key at this position
495      * @throws IndexOutOfBoundsException when the ask for the position is invalid
496      */
497     public K getKey(final int index) {
498         if (index < 0 || index >= this.orderedListSize_) {
499             throw new IndexOutOfBoundsException(String.format("Index: %s, Size: %s", index, this.orderedListSize_));
500         }
501 
502         final int pos = this.orderedList_[index];
503         return (K) this.mapData_[pos];
504     }
505 
506     /**
507      * Returns the value at a certain position of the ordered list that
508      * keeps the addition order of this map.
509      *
510      * @param index the position to fetch
511      * @return the value at this position
512      * @throws IndexOutOfBoundsException when the ask for the position is invalid
513      */
514     public V getValue(final int index) {
515         if (index < 0 || index >= this.orderedListSize_) {
516             throw new IndexOutOfBoundsException(String.format("Index: %s, Size: %s", index, this.orderedListSize_));
517         }
518 
519         final int pos = this.orderedList_[index];
520         return (V) this.mapData_[pos + 1];
521     }
522 
523     /**
524      * Removes a key and value from this map based on the position
525      * in the backing list, rather by key as usual.
526      *
527      * @param index the position to remove the data from
528      * @return the value stored
529      * @throws IndexOutOfBoundsException when the ask for the position is invalid
530      */
531     public V remove(final int index) {
532         if (index < 0 || index >= this.orderedListSize_) {
533             throw new IndexOutOfBoundsException(String.format("Index: %s, Size: %s", index, this.orderedListSize_));
534         }
535 
536         final int pos = this.orderedList_[index];
537         final K key = (K) this.mapData_[pos];
538 
539         return remove(key);
540     }
541 
542     @Override
543     public V put(final K key, final V value) {
544         return this.put(key, value, Position.LAST);
545     }
546 
547     /**
548      * Insert at the beginning.
549      * @param key the key
550      * @param value the value
551      * @return the inserted value
552      */
553     public V addFirst(final K key, final V value) {
554         return this.put(key, value, Position.FIRST);
555     }
556 
557     /**
558      * Append at the end.
559      * @param key the key
560      * @param value the value
561      * @return the appended value
562      */
563     public V add(final K key, final V value) {
564         return this.put(key, value, Position.LAST);
565     }
566 
567     /**
568      * Append at the end.
569      * @param key the key
570      * @param value the value
571      * @return the appended value
572      */
573     public V addLast(final K key, final V value) {
574         return this.put(key, value, Position.LAST);
575     }
576 
577     /**
578      * @return the first value.
579      */
580     public V getFirst() {
581         return getValue(0);
582     }
583 
584     /**
585      * @return the last value.
586      */
587     public V getLast() {
588         return getValue(this.orderedListSize_ - 1);
589     }
590 
591     /**
592      * Removes the first entry.
593      * @return the removed value or null if the map was empty.
594      */
595     public V removeFirst() {
596         if (this.orderedListSize_ > 0) {
597             final int pos = this.orderedList_[0];
598             final K key = (K) this.mapData_[pos];
599             return remove(key);
600         }
601         return null;
602     }
603 
604     /**
605      * Removes the last entry.
606      * @return the removed value or null if the map was empty.
607      */
608     public V removeLast() {
609         if (this.orderedListSize_ > 0) {
610             final int pos = this.orderedList_[this.orderedListSize_ - 1];
611             final K key = (K) this.mapData_[pos];
612             return remove(key);
613         }
614         return null;
615     }
616 
617     /**
618      * Checks of a key is in the map.
619      *
620      * @param key the key to check
621      * @return true of the key is in the map, false otherwise
622      */
623     @Override
624     public boolean containsKey(final Object key) {
625         return get(key) != null;
626     }
627 
628     @Override
629     public boolean containsValue(final Object value) {
630         // that is expensive, we have to iterate everything
631         for (int i = 0; i < this.orderedListSize_; i++) {
632             final int pos = this.orderedList_[i] + 1;
633             final Object v = this.mapData_[pos];
634 
635             // do we match?
636             if (v == value || v.equals(value)) {
637                 return true;
638             }
639         }
640 
641         return false;
642     }
643 
644     @Override
645     public boolean isEmpty() {
646         return this.mapSize_ == 0;
647     }
648 
649     @Override
650     public Set<Map.Entry<K, V>> entrySet() {
651         return new OrderedEntrySet<>(this);
652     }
653 
654     @Override
655     public Set<K> keySet() {
656         return new OrderedKeySet<>(this);
657     }
658 
659     /**
660      * Just reverses the ordering of the map as created so far.
661      */
662     public void reverse() {
663         // In-place reversal
664         final int to = this.orderedListSize_ / 2;
665 
666         for (int i = 0; i < to; i++) {
667             // Swapping the elements
668             final int j = this.orderedList_[i];
669             this.orderedList_[i] = this.orderedList_[this.orderedListSize_ - i - 1];
670             this.orderedList_[this.orderedListSize_ - i - 1] = j;
671         }
672     }
673 
674     /**
675      * We have to overwrite the export due to the use of static object as marker
676      *
677      * @param aInputStream the inputstream to read from
678      * @throws IOException when the reading from the source fails
679      * @throws ClassNotFoundException in case we cannot restore a class
680      */
681     private void readObject(final ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
682         // perform the default de-serialization first
683         aInputStream.defaultReadObject();
684 
685         // we have to restore order, keep relevant data
686         final Object[] srcData = Arrays.copyOf(this.mapData_, this.mapData_.length);
687         final int[] srcIndex = Arrays.copyOf(this.orderedList_, this.orderedList_.length);
688         final int orderedListSize = this.orderedListSize_;
689 
690         // now, empty the original map
691         clear();
692 
693         // sort things in, so we get a nice clean new map, this will
694         // also cleanup what was previously a removed entry, we have not
695         // kept that information anyway
696         for (int i = 0; i < orderedListSize; i++) {
697             final int pos = srcIndex[i];
698 
699             final K key = (K) srcData[pos];
700             final V value = (V) srcData[pos + 1];
701             put(key, value);
702         }
703     }
704 
705     /**
706      * We have to overwrite the export due to the use of static object as marker
707      *
708      * @param aOutputStream the stream to write to
709      * @throws IOException in case we have issue writing our data to the stream
710      */
711     private void writeObject(final ObjectOutputStream aOutputStream) throws IOException {
712         // we will remove all placeholder object references,
713         // when putting it back together, we rebuild the map from scratch
714         for (int i = 0; i < this.mapData_.length; i++) {
715             final Object entry = this.mapData_[i];
716             if (entry == FREE_KEY_ || entry == REMOVED_KEY_) {
717                 this.mapData_[i] = null;
718             }
719         }
720 
721         // perform the default serialization for all non-transient, non-static fields
722         aOutputStream.defaultWriteObject();
723     }
724 
725     /**
726      * This set does not support any modifications through its interface. All such
727      * methods will throw {@link UnsupportedOperationException}.
728      */
729     static class OrderedEntrySet<K, V> implements Set<Map.Entry<K, V>> {
730         private final OrderedFastHashMap<K, V> backingMap_;
731 
732         OrderedEntrySet(final OrderedFastHashMap<K, V> backingMap) {
733             this.backingMap_ = backingMap;
734         }
735 
736         @Override
737         public int size() {
738             return this.backingMap_.size();
739         }
740 
741         @Override
742         public boolean isEmpty() {
743             return this.backingMap_.isEmpty();
744         }
745 
746         @Override
747         public boolean contains(final Object o) {
748             if (o instanceof Map.Entry) {
749                 final Map.Entry ose = (Map.Entry) o;
750                 final Object k = ose.getKey();
751                 final Object v = ose.getValue();
752 
753                 final Object value = this.backingMap_.get(k);
754                 if (value != null) {
755                     return v.equals(value);
756                 }
757             }
758 
759             return false;
760         }
761 
762         @Override
763         public Object[] toArray() {
764             final Object[] array = new Object[this.backingMap_.orderedListSize_];
765             return toArray(array);
766         }
767 
768         @Override
769         @SuppressWarnings("unchecked")
770         public <T> T[] toArray(final T[] a) {
771             final T[] array;
772             if (a.length >= this.backingMap_.orderedListSize_) {
773                 array = a;
774             }
775             else {
776                 array = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),
777                         this.backingMap_.orderedListSize_);
778             }
779 
780             for (int i = 0; i < this.backingMap_.orderedListSize_; i++) {
781                 array[i] = (T) this.backingMap_.getEntry(i);
782             }
783 
784             return array;
785         }
786 
787         @Override
788         public Iterator<Map.Entry<K, V>> iterator() {
789             return new OrderedEntryIterator();
790         }
791 
792         @Override
793         public boolean add(final Map.Entry<K, V> e) {
794             throw new UnsupportedOperationException();
795         }
796 
797         @Override
798         public boolean remove(final Object o) {
799             throw new UnsupportedOperationException();
800         }
801 
802         @Override
803         public boolean containsAll(final Collection<?> c) {
804             throw new UnsupportedOperationException();
805         }
806 
807         @Override
808         public boolean addAll(final Collection<? extends Map.Entry<K, V>> c) {
809             throw new UnsupportedOperationException();
810         }
811 
812         @Override
813         public boolean retainAll(final Collection<?> c) {
814             throw new UnsupportedOperationException();
815         }
816 
817         @Override
818         public boolean removeAll(final Collection<?> c) {
819             throw new UnsupportedOperationException();
820         }
821 
822         @Override
823         public void clear() {
824             throw new UnsupportedOperationException();
825         }
826 
827         class OrderedEntryIterator implements Iterator<Map.Entry<K, V>> {
828             private int pos_ = 0;
829 
830             @Override
831             public boolean hasNext() {
832                 return pos_ < backingMap_.orderedListSize_;
833             }
834 
835             @Override
836             public Map.Entry<K, V> next() {
837                 if (pos_ < backingMap_.orderedListSize_) {
838                     return backingMap_.getEntry(pos_++);
839                 }
840                 throw new NoSuchElementException();
841             }
842         }
843     }
844 
845     static class OrderedKeySet<K, V> implements Set<K> {
846         private final OrderedFastHashMap<K, V> backingMap_;
847 
848         OrderedKeySet(final OrderedFastHashMap<K, V> backingMap) {
849             this.backingMap_ = backingMap;
850         }
851 
852         @Override
853         public int size() {
854             return this.backingMap_.size();
855         }
856 
857         @Override
858         public boolean isEmpty() {
859             return this.backingMap_.isEmpty();
860         }
861 
862         @Override
863         public boolean contains(final Object o) {
864             return this.backingMap_.containsKey(o);
865         }
866 
867         @Override
868         public Object[] toArray() {
869             final Object[] array = new Object[this.backingMap_.orderedListSize_];
870             return toArray(array);
871         }
872 
873         @Override
874         @SuppressWarnings("unchecked")
875         public <T> T[] toArray(final T[] a) {
876             final T[] array;
877 
878             if (a.length >= this.backingMap_.orderedListSize_) {
879                 array = a;
880             }
881             else {
882                 array = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),
883                         this.backingMap_.orderedListSize_);
884             }
885 
886             for (int i = 0; i < this.backingMap_.orderedListSize_; i++) {
887                 array[i] = (T) this.backingMap_.getKey(i);
888             }
889 
890             return array;
891         }
892 
893         @Override
894         public Iterator<K> iterator() {
895             return new OrderedKeyIterator();
896         }
897 
898         class OrderedKeyIterator implements Iterator<K> {
899             private int pos_ = 0;
900 
901             @Override
902             public boolean hasNext() {
903                 return this.pos_ < backingMap_.orderedListSize_;
904             }
905 
906             @Override
907             public K next() {
908                 if (this.pos_ < backingMap_.orderedListSize_) {
909                     return backingMap_.getKey(this.pos_++);
910                 }
911                 throw new NoSuchElementException();
912             }
913         }
914 
915         @Override
916         public boolean add(final K e) {
917             throw new UnsupportedOperationException();
918         }
919 
920         @Override
921         public boolean remove(final Object o) {
922             throw new UnsupportedOperationException();
923         }
924 
925         @Override
926         public boolean containsAll(final Collection<?> c) {
927             throw new UnsupportedOperationException();
928         }
929 
930         @Override
931         public boolean addAll(final Collection<? extends K> c) {
932             throw new UnsupportedOperationException();
933         }
934 
935         @Override
936         public boolean retainAll(final Collection<?> c) {
937             throw new UnsupportedOperationException();
938         }
939 
940         @Override
941         public boolean removeAll(final Collection<?> c) {
942             throw new UnsupportedOperationException();
943         }
944 
945         @Override
946         public void clear() {
947             throw new UnsupportedOperationException();
948         }
949     }
950 
951     @Override
952     public void putAll(final Map<? extends K, ? extends V> src) {
953         if (src == this) {
954             throw new IllegalArgumentException("Cannot add myself");
955         }
956 
957         for (final Map.Entry<? extends K, ? extends V> entry : src.entrySet()) {
958             put(entry.getKey(), entry.getValue(), Position.LAST);
959         }
960     }
961 
962     private void orderedListAdd(final Position listPosition, final int position) {
963         // the list should still have room, otherwise the map was
964         // grown already and the ordering list with it
965         if (listPosition == Position.FIRST) {
966             System.arraycopy(this.orderedList_, 0, this.orderedList_, 1, this.orderedList_.length - 1);
967             this.orderedList_[0] = position;
968             this.orderedListSize_++;
969         }
970         else if (listPosition == Position.LAST) {
971             this.orderedList_[this.orderedListSize_] = position;
972             this.orderedListSize_++;
973         }
974         else {
975             // if none, we are rebuilding the map and don't have to do a thing
976         }
977     }
978 
979     private void orderedListRemove(final int position) {
980         // find the positional information
981         int i = 0;
982         for ( ; i < this.orderedListSize_; i++) {
983             if (this.orderedList_[i] == position) {
984                 this.orderedList_[i] = -1;
985                 if (i < this.orderedListSize_) {
986                     // not the last element, compact
987                     System.arraycopy(this.orderedList_, i + 1, this.orderedList_, i, this.orderedListSize_ - i);
988                 }
989                 this.orderedListSize_--;
990 
991                 return;
992             }
993         }
994 
995         if (i == this.orderedListSize_) {
996             throw new IllegalArgumentException(String.format("Position %s was not in order list", position));
997         }
998     }
999 
1000     @Override
1001     public String toString() {
1002         final int maxLen = 10;
1003 
1004         return String.format(
1005                 "mapData=%s, mapFillFactor=%s, mapThreshold=%s, mapSize=%s,%norderedList=%s, orderedListSize=%s",
1006                 mapData_ != null ? Arrays.asList(mapData_).subList(0, Math.min(mapData_.length, maxLen)) : null,
1007                         FILLFACTOR_, mapThreshold_, mapSize_,
1008                         orderedList_ != null
1009                         ? Arrays.toString(Arrays.copyOf(orderedList_, Math.min(orderedList_.length, maxLen)))
1010                                 : null,
1011                                 orderedListSize_);
1012     }
1013 
1014     /**
1015      * Helper for identifying if we need to position our new entry differently.
1016      */
1017     private enum Position {
1018         NONE, FIRST, LAST
1019     }
1020 
1021     /**
1022      * Well, we need that to satisfy the map implementation concept.
1023      *
1024      * @param <K> the key
1025      * @param <V> the value
1026      */
1027     static class Entry<K, V> implements Map.Entry<K, V> {
1028         private final K key_;
1029         private final V value_;
1030 
1031         Entry(final K key, final V value) {
1032             this.key_ = key;
1033             this.value_ = value;
1034         }
1035 
1036         @Override
1037         public K getKey() {
1038             return key_;
1039         }
1040 
1041         @Override
1042         public V getValue() {
1043             return value_;
1044         }
1045 
1046         @Override
1047         public V setValue(final V value) {
1048             throw new UnsupportedOperationException("This map does not support write-through via an entry");
1049         }
1050 
1051         @Override
1052         public int hashCode() {
1053             return Objects.hashCode(key_) ^ Objects.hashCode(value_);
1054         }
1055 
1056         @Override
1057         public String toString() {
1058             return key_ + "=" + value_;
1059         }
1060 
1061         @Override
1062         public boolean equals(final Object o) {
1063             if (o == this) {
1064                 return true;
1065             }
1066 
1067             if (o instanceof Map.Entry) {
1068                 final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
1069 
1070                 if (Objects.equals(key_, e.getKey()) && Objects.equals(value_, e.getValue())) {
1071                     return true;
1072                 }
1073             }
1074 
1075             return false;
1076         }
1077     }
1078 }