1
2
3
4
5
6
7
8
9
10
11
12
13
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public class OrderedFastHashMap<K, V> implements Map<K, V>, Serializable {
67
68 private static final Object FREE_KEY_ = null;
69 private static final Object REMOVED_KEY_ = new Object();
70
71
72 private static final double FILLFACTOR_ = 0.7d;
73
74
75 private Object[] mapData_;
76
77
78 private int mapThreshold_;
79
80
81 private int mapSize_;
82
83
84
85
86 private int[] orderedList_;
87
88
89
90 private int orderedListSize_;
91
92
93
94
95 public OrderedFastHashMap() {
96 this(8);
97 }
98
99
100
101
102
103
104
105 public OrderedFastHashMap(final int size) {
106 if (size > 0) {
107 final int capacity = arraySize(size, FILLFACTOR_);
108
109 this.mapData_ = new Object[capacity << 1];
110 this.mapThreshold_ = (int) (capacity * FILLFACTOR_);
111
112 this.orderedList_ = new int[capacity];
113 }
114 else {
115 this.mapData_ = new Object[0];
116 this.mapThreshold_ = 0;
117
118 this.orderedList_ = new int[0];
119 }
120 }
121
122
123
124
125
126
127
128
129 @Override
130 public V get(final Object key) {
131 final int length = this.mapData_.length;
132
133
134 if (length == 0) {
135 return null;
136 }
137
138 int ptr = (key.hashCode() & ((length >> 1) - 1)) << 1;
139 Object k = mapData_[ptr];
140
141 if (k == FREE_KEY_) {
142 return null;
143 }
144
145
146 if (k.hashCode() == key.hashCode() && k.equals(key)) {
147 return (V) this.mapData_[ptr + 1];
148 }
149
150
151 final int originalPtr = ptr;
152 while (true) {
153 ptr = (ptr + 2) & (length - 1);
154
155
156 if (originalPtr == ptr) {
157 return null;
158 }
159
160 k = this.mapData_[ptr];
161
162 if (k == FREE_KEY_) {
163 return null;
164 }
165
166 if (k != REMOVED_KEY_) {
167 if (k.hashCode() == key.hashCode() && k.equals(key)) {
168 return (V) this.mapData_[ptr + 1];
169 }
170 }
171 }
172 }
173
174
175
176
177
178
179
180
181
182
183 private V put(final K key, final V value, final Position listPosition) {
184 if (mapSize_ >= mapThreshold_) {
185 rehash(this.mapData_.length == 0 ? 4 : this.mapData_.length << 1);
186 }
187
188 int ptr = getStartIndex(key) << 1;
189 Object k = mapData_[ptr];
190
191 if (k == FREE_KEY_) {
192
193 mapData_[ptr] = key;
194 mapData_[ptr + 1] = value;
195
196
197 orderedListAdd(listPosition, ptr);
198
199 mapSize_++;
200
201 return null;
202 }
203 else if (k.equals(key)) {
204
205 final Object ret = mapData_[ptr + 1];
206 mapData_[ptr + 1] = value;
207
208
209
210 return (V) ret;
211 }
212
213 int firstRemoved = -1;
214 if (k == REMOVED_KEY_) {
215 firstRemoved = ptr;
216 }
217
218 while (true) {
219 ptr = (ptr + 2) & (this.mapData_.length - 1);
220 k = mapData_[ptr];
221
222 if (k == FREE_KEY_) {
223 if (firstRemoved != -1) {
224 ptr = firstRemoved;
225 }
226 mapData_[ptr] = key;
227 mapData_[ptr + 1] = value;
228
229
230 orderedListAdd(listPosition, ptr);
231
232 mapSize_++;
233
234 return null;
235 }
236 else if (k.equals(key)) {
237 final Object ret = mapData_[ptr + 1];
238 mapData_[ptr + 1] = value;
239
240
241
242 return (V) ret;
243 }
244 else if (k == REMOVED_KEY_) {
245 if (firstRemoved == -1) {
246 firstRemoved = ptr;
247 }
248 }
249 }
250 }
251
252
253
254
255
256
257
258
259 @Override
260 public V remove(final Object key) {
261 final int length = this.mapData_.length;
262
263 if (length == 0) {
264 return null;
265 }
266
267 int ptr = getStartIndex(key) << 1;
268 Object k = this.mapData_[ptr];
269
270 if (k == FREE_KEY_) {
271 return null;
272 }
273 else if (k.equals(key)) {
274
275 this.mapSize_--;
276
277 if (this.mapData_[(ptr + 2) & (length - 1)] == FREE_KEY_) {
278 this.mapData_[ptr] = FREE_KEY_;
279 }
280 else {
281 this.mapData_[ptr] = REMOVED_KEY_;
282 }
283
284 final V ret = (V) this.mapData_[ptr + 1];
285 this.mapData_[ptr + 1] = null;
286
287
288 orderedListRemove(ptr);
289
290 return ret;
291 }
292
293 while (true) {
294 ptr = (ptr + 2) & (length - 1);
295 k = this.mapData_[ptr];
296
297 if (k == FREE_KEY_) {
298 return null;
299 }
300 else if (k.equals(key)) {
301 this.mapSize_--;
302 if (this.mapData_[(ptr + 2) & (length - 1)] == FREE_KEY_) {
303 this.mapData_[ptr] = FREE_KEY_;
304 }
305 else {
306 this.mapData_[ptr] = REMOVED_KEY_;
307 }
308
309 final V ret = (V) this.mapData_[ptr + 1];
310 this.mapData_[ptr + 1] = null;
311
312
313 orderedListRemove(ptr);
314
315 return ret;
316 }
317 }
318 }
319
320
321
322
323
324
325 @Override
326 public int size() {
327 return mapSize_;
328 }
329
330
331
332
333
334
335 private void rehash(final int newCapacity) {
336 this.mapThreshold_ = (int) (newCapacity / 2 * FILLFACTOR_);
337
338 final Object[] oldData = this.mapData_;
339
340 this.mapData_ = new Object[newCapacity];
341
342
343
344 final int[] oldOrderedList = this.orderedList_;
345 final int oldOrderedListSize = this.orderedListSize_;
346 this.orderedList_ = new int[newCapacity];
347
348 this.mapSize_ = 0;
349 this.orderedListSize_ = 0;
350
351
352
353
354
355 for (int i = 0; i < oldOrderedListSize; i++) {
356 final int pos = oldOrderedList[i];
357
358
359 final K key = (K) oldData[pos];
360 final V value = (V) oldData[pos + 1];
361
362
363
364 put(key, value, Position.LAST);
365 }
366 }
367
368
369
370
371
372
373
374
375
376 public List<K> keys() {
377 final List<K> result = new ArrayList<>(this.orderedListSize_);
378
379 for (int i = 0; i < this.orderedListSize_; i++) {
380 final int pos = this.orderedList_[i];
381 final Object o = this.mapData_[pos];
382 result.add((K) o);
383 }
384
385 return result;
386 }
387
388
389
390
391
392
393
394
395
396 @Override
397 public List<V> values() {
398 final List<V> result = new ArrayList<>(this.orderedListSize_);
399
400 for (int i = 0; i < this.orderedListSize_; i++) {
401 final int pos = this.orderedList_[i];
402 final Object o = this.mapData_[pos + 1];
403 result.add((V) o);
404 }
405
406 return result;
407 }
408
409
410
411
412
413 @Override
414 public void clear() {
415 this.mapSize_ = 0;
416 this.orderedListSize_ = 0;
417 Arrays.fill(this.mapData_, FREE_KEY_);
418
419 }
420
421
422
423
424
425
426
427 private int getStartIndex(final Object key) {
428
429 return key.hashCode() & ((this.mapData_.length >> 1) - 1);
430 }
431
432
433
434
435
436
437
438
439
440
441
442 private static long nextPowerOfTwo(final long x) {
443 if (x == 0) {
444 return 1;
445 }
446
447 long r = x - 1;
448 r |= r >> 1;
449 r |= r >> 2;
450 r |= r >> 4;
451 r |= r >> 8;
452 r |= r >> 16;
453
454 return (r | r >> 32) + 1;
455 }
456
457
458
459
460
461
462
463
464
465
466
467 private static int arraySize(final int expected, final double f) {
468 final long s = Math.max(2, nextPowerOfTwo((long) Math.ceil(expected / f)));
469
470 if (s > (1 << 30)) {
471 throw new IllegalArgumentException(
472 "Too large (" + expected + " expected elements with load factor " + f + ")");
473 }
474 return (int) s;
475 }
476
477
478
479
480
481
482
483
484
485
486 public Entry<K, V> getEntry(final int index) {
487 if (index < 0 || index >= this.orderedListSize_) {
488 throw new IndexOutOfBoundsException("Index: %s, Size: %s".formatted(index, this.orderedListSize_));
489 }
490
491 final int pos = this.orderedList_[index];
492 return new Entry(this.mapData_[pos], this.mapData_[pos + 1]);
493 }
494
495
496
497
498
499
500
501
502
503 public K getKey(final int index) {
504 if (index < 0 || index >= this.orderedListSize_) {
505 throw new IndexOutOfBoundsException("Index: %s, Size: %s".formatted(index, this.orderedListSize_));
506 }
507
508 final int pos = this.orderedList_[index];
509 return (K) this.mapData_[pos];
510 }
511
512
513
514
515
516
517
518
519
520 public V getValue(final int index) {
521 if (index < 0 || index >= this.orderedListSize_) {
522 throw new IndexOutOfBoundsException("Index: %s, Size: %s".formatted(index, this.orderedListSize_));
523 }
524
525 final int pos = this.orderedList_[index];
526 return (V) this.mapData_[pos + 1];
527 }
528
529
530
531
532
533
534
535
536
537 public V remove(final int index) {
538 if (index < 0 || index >= this.orderedListSize_) {
539 throw new IndexOutOfBoundsException("Index: %s, Size: %s".formatted(index, this.orderedListSize_));
540 }
541
542 final int pos = this.orderedList_[index];
543 final K key = (K) this.mapData_[pos];
544
545 return remove(key);
546 }
547
548 @Override
549 public V put(final K key, final V value) {
550 return this.put(key, value, Position.LAST);
551 }
552
553
554
555
556
557
558
559 public V addFirst(final K key, final V value) {
560 return this.put(key, value, Position.FIRST);
561 }
562
563
564
565
566
567
568
569 public V add(final K key, final V value) {
570 return this.put(key, value, Position.LAST);
571 }
572
573
574
575
576
577
578
579 public V addLast(final K key, final V value) {
580 return this.put(key, value, Position.LAST);
581 }
582
583
584
585
586
587
588 public V getFirst() {
589 return getValue(0);
590 }
591
592
593
594
595
596
597 public V getLast() {
598 return getValue(this.orderedListSize_ - 1);
599 }
600
601
602
603
604
605 public V removeFirst() {
606 if (this.orderedListSize_ > 0) {
607 final int pos = this.orderedList_[0];
608 final K key = (K) this.mapData_[pos];
609 return remove(key);
610 }
611 return null;
612 }
613
614
615
616
617
618 public V removeLast() {
619 if (this.orderedListSize_ > 0) {
620 final int pos = this.orderedList_[this.orderedListSize_ - 1];
621 final K key = (K) this.mapData_[pos];
622 return remove(key);
623 }
624 return null;
625 }
626
627
628
629
630
631
632
633 @Override
634 public boolean containsKey(final Object key) {
635 return get(key) != null;
636 }
637
638 @Override
639 public boolean containsValue(final Object value) {
640
641 for (int i = 0; i < this.orderedListSize_; i++) {
642 final int pos = this.orderedList_[i] + 1;
643 final Object v = this.mapData_[pos];
644
645
646 if (v == value || v.equals(value)) {
647 return true;
648 }
649 }
650
651 return false;
652 }
653
654 @Override
655 public boolean isEmpty() {
656 return this.mapSize_ == 0;
657 }
658
659 @Override
660 public Set<Map.Entry<K, V>> entrySet() {
661 return new OrderedEntrySet<>(this);
662 }
663
664 @Override
665 public Set<K> keySet() {
666 return new OrderedKeySet<>(this);
667 }
668
669
670
671
672 public void reverse() {
673
674 final int to = this.orderedListSize_ / 2;
675
676 for (int i = 0; i < to; i++) {
677
678 final int j = this.orderedList_[i];
679 this.orderedList_[i] = this.orderedList_[this.orderedListSize_ - i - 1];
680 this.orderedList_[this.orderedListSize_ - i - 1] = j;
681 }
682 }
683
684
685
686
687
688
689
690
691 private void readObject(final ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
692
693 aInputStream.defaultReadObject();
694
695
696 final Object[] srcData = Arrays.copyOf(this.mapData_, this.mapData_.length);
697 final int[] srcIndex = Arrays.copyOf(this.orderedList_, this.orderedList_.length);
698 final int orderedListSize = this.orderedListSize_;
699
700
701 clear();
702
703
704
705
706 for (int i = 0; i < orderedListSize; i++) {
707 final int pos = srcIndex[i];
708
709 final K key = (K) srcData[pos];
710 final V value = (V) srcData[pos + 1];
711 put(key, value);
712 }
713 }
714
715
716
717
718
719
720
721 private void writeObject(final ObjectOutputStream aOutputStream) throws IOException {
722
723
724 for (int i = 0; i < this.mapData_.length; i++) {
725 final Object entry = this.mapData_[i];
726 if (entry == FREE_KEY_ || entry == REMOVED_KEY_) {
727 this.mapData_[i] = null;
728 }
729 }
730
731
732 aOutputStream.defaultWriteObject();
733 }
734
735
736
737
738
739 static class OrderedEntrySet<K, V> implements Set<Map.Entry<K, V>> {
740 private final OrderedFastHashMap<K, V> backingMap_;
741
742 OrderedEntrySet(final OrderedFastHashMap<K, V> backingMap) {
743 this.backingMap_ = backingMap;
744 }
745
746 @Override
747 public int size() {
748 return this.backingMap_.size();
749 }
750
751 @Override
752 public boolean isEmpty() {
753 return this.backingMap_.isEmpty();
754 }
755
756 @Override
757 public boolean contains(final Object o) {
758 if (o instanceof Map.Entry ose) {
759 final Object k = ose.getKey();
760 final Object v = ose.getValue();
761
762 final Object value = this.backingMap_.get(k);
763 if (value != null) {
764 return v.equals(value);
765 }
766 }
767
768 return false;
769 }
770
771 @Override
772 public Object[] toArray() {
773 final Object[] array = new Object[this.backingMap_.orderedListSize_];
774 return toArray(array);
775 }
776
777 @Override
778 @SuppressWarnings("unchecked")
779 public <T> T[] toArray(final T[] a) {
780 final T[] array;
781 if (a.length >= this.backingMap_.orderedListSize_) {
782 array = a;
783 }
784 else {
785 array = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),
786 this.backingMap_.orderedListSize_);
787 }
788
789 for (int i = 0; i < this.backingMap_.orderedListSize_; i++) {
790 array[i] = (T) this.backingMap_.getEntry(i);
791 }
792
793 return array;
794 }
795
796 @Override
797 public Iterator<Map.Entry<K, V>> iterator() {
798 return new OrderedEntryIterator();
799 }
800
801 @Override
802 public boolean add(final Map.Entry<K, V> e) {
803 throw new UnsupportedOperationException();
804 }
805
806 @Override
807 public boolean remove(final Object o) {
808 throw new UnsupportedOperationException();
809 }
810
811 @Override
812 public boolean containsAll(final Collection<?> c) {
813 throw new UnsupportedOperationException();
814 }
815
816 @Override
817 public boolean addAll(final Collection<? extends Map.Entry<K, V>> c) {
818 throw new UnsupportedOperationException();
819 }
820
821 @Override
822 public boolean retainAll(final Collection<?> c) {
823 throw new UnsupportedOperationException();
824 }
825
826 @Override
827 public boolean removeAll(final Collection<?> c) {
828 throw new UnsupportedOperationException();
829 }
830
831 @Override
832 public void clear() {
833 throw new UnsupportedOperationException();
834 }
835
836 class OrderedEntryIterator implements Iterator<Map.Entry<K, V>> {
837 private int pos_ = 0;
838
839 @Override
840 public boolean hasNext() {
841 return pos_ < backingMap_.orderedListSize_;
842 }
843
844 @Override
845 public Map.Entry<K, V> next() {
846 if (pos_ < backingMap_.orderedListSize_) {
847 return backingMap_.getEntry(pos_++);
848 }
849 throw new NoSuchElementException();
850 }
851 }
852 }
853
854 static class OrderedKeySet<K, V> implements Set<K> {
855 private final OrderedFastHashMap<K, V> backingMap_;
856
857 OrderedKeySet(final OrderedFastHashMap<K, V> backingMap) {
858 this.backingMap_ = backingMap;
859 }
860
861 @Override
862 public int size() {
863 return this.backingMap_.size();
864 }
865
866 @Override
867 public boolean isEmpty() {
868 return this.backingMap_.isEmpty();
869 }
870
871 @Override
872 public boolean contains(final Object o) {
873 return this.backingMap_.containsKey(o);
874 }
875
876 @Override
877 public Object[] toArray() {
878 final Object[] array = new Object[this.backingMap_.orderedListSize_];
879 return toArray(array);
880 }
881
882 @Override
883 @SuppressWarnings("unchecked")
884 public <T> T[] toArray(final T[] a) {
885 final T[] array;
886
887 if (a.length >= this.backingMap_.orderedListSize_) {
888 array = a;
889 }
890 else {
891 array = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),
892 this.backingMap_.orderedListSize_);
893 }
894
895 for (int i = 0; i < this.backingMap_.orderedListSize_; i++) {
896 array[i] = (T) this.backingMap_.getKey(i);
897 }
898
899 return array;
900 }
901
902 @Override
903 public Iterator<K> iterator() {
904 return new OrderedKeyIterator();
905 }
906
907 class OrderedKeyIterator implements Iterator<K> {
908 private int pos_ = 0;
909
910 @Override
911 public boolean hasNext() {
912 return this.pos_ < backingMap_.orderedListSize_;
913 }
914
915 @Override
916 public K next() {
917 if (this.pos_ < backingMap_.orderedListSize_) {
918 return backingMap_.getKey(this.pos_++);
919 }
920 throw new NoSuchElementException();
921 }
922 }
923
924 @Override
925 public boolean add(final K e) {
926 throw new UnsupportedOperationException();
927 }
928
929 @Override
930 public boolean remove(final Object o) {
931 throw new UnsupportedOperationException();
932 }
933
934 @Override
935 public boolean containsAll(final Collection<?> c) {
936 throw new UnsupportedOperationException();
937 }
938
939 @Override
940 public boolean addAll(final Collection<? extends K> c) {
941 throw new UnsupportedOperationException();
942 }
943
944 @Override
945 public boolean retainAll(final Collection<?> c) {
946 throw new UnsupportedOperationException();
947 }
948
949 @Override
950 public boolean removeAll(final Collection<?> c) {
951 throw new UnsupportedOperationException();
952 }
953
954 @Override
955 public void clear() {
956 throw new UnsupportedOperationException();
957 }
958 }
959
960 @Override
961 public void putAll(final Map<? extends K, ? extends V> src) {
962 if (src == this) {
963 throw new IllegalArgumentException("Cannot add myself");
964 }
965
966 for (final Map.Entry<? extends K, ? extends V> entry : src.entrySet()) {
967 put(entry.getKey(), entry.getValue(), Position.LAST);
968 }
969 }
970
971 private void orderedListAdd(final Position listPosition, final int position) {
972
973
974 if (listPosition == Position.FIRST) {
975 System.arraycopy(this.orderedList_, 0, this.orderedList_, 1, this.orderedList_.length - 1);
976 this.orderedList_[0] = position;
977 this.orderedListSize_++;
978 }
979 else if (listPosition == Position.LAST) {
980 this.orderedList_[this.orderedListSize_] = position;
981 this.orderedListSize_++;
982 }
983 else {
984
985 }
986 }
987
988 private void orderedListRemove(final int position) {
989
990 int i = 0;
991 for ( ; i < this.orderedListSize_; i++) {
992 if (this.orderedList_[i] == position) {
993 this.orderedList_[i] = -1;
994 if (i < this.orderedListSize_) {
995
996 System.arraycopy(this.orderedList_, i + 1, this.orderedList_, i, this.orderedListSize_ - i);
997 }
998 this.orderedListSize_--;
999
1000 return;
1001 }
1002 }
1003
1004 if (i == this.orderedListSize_) {
1005 throw new IllegalArgumentException("Position %s was not in order list".formatted(position));
1006 }
1007 }
1008
1009 @Override
1010 public String toString() {
1011 final int maxLen = 10;
1012
1013 return "mapData=%s, mapFillFactor=%s, mapThreshold=%s, mapSize=%s,%norderedList=%s, orderedListSize=%s"
1014 .formatted(
1015 mapData_ != null
1016 ? Arrays.asList(mapData_).subList(0, Math.min(mapData_.length, maxLen))
1017 : null,
1018 FILLFACTOR_, mapThreshold_, mapSize_,
1019 orderedList_ != null
1020 ? Arrays.toString(Arrays.copyOf(orderedList_, Math.min(orderedList_.length, maxLen)))
1021 : null,
1022 orderedListSize_);
1023 }
1024
1025
1026
1027
1028 private enum Position {
1029 NONE, FIRST, LAST
1030 }
1031
1032
1033
1034
1035
1036
1037
1038 static class Entry<K, V> implements Map.Entry<K, V> {
1039 private final K key_;
1040 private final V value_;
1041
1042 Entry(final K key, final V value) {
1043 this.key_ = key;
1044 this.value_ = value;
1045 }
1046
1047 @Override
1048 public K getKey() {
1049 return key_;
1050 }
1051
1052 @Override
1053 public V getValue() {
1054 return value_;
1055 }
1056
1057 @Override
1058 public V setValue(final V value) {
1059 throw new UnsupportedOperationException("This map does not support write-through via an entry");
1060 }
1061
1062 @Override
1063 public int hashCode() {
1064 return Objects.hashCode(key_) ^ Objects.hashCode(value_);
1065 }
1066
1067 @Override
1068 public String toString() {
1069 return key_ + "=" + value_;
1070 }
1071
1072 @Override
1073 public boolean equals(final Object o) {
1074 if (o == this) {
1075 return true;
1076 }
1077
1078 if (o instanceof Map.Entry<?, ?> e) {
1079
1080 if (Objects.equals(key_, e.getKey()) && Objects.equals(value_, e.getValue())) {
1081 return true;
1082 }
1083 }
1084
1085 return false;
1086 }
1087 }
1088 }