/jboss-as-7.1.1.Final/controller/src/main/java/org/jboss/as/controller/registry/FastCopyHashMap.java
Java | 759 lines | 524 code | 155 blank | 80 comment | 130 complexity | ae917ae03c3d2b28ac57d2a82b476def MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
1/*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2011, Red Hat, Inc., and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22
23package org.jboss.as.controller.registry;
24
25import java.io.IOException;
26import java.io.Serializable;
27import java.util.AbstractCollection;
28import java.util.AbstractMap;
29import java.util.AbstractSet;
30import java.util.Collection;
31import java.util.ConcurrentModificationException;
32import java.util.Iterator;
33import java.util.Map;
34import java.util.NoSuchElementException;
35import java.util.Set;
36
37import static org.jboss.as.controller.ControllerMessages.MESSAGES;
38
39/**
40 * A HashMap that is optimized for fast shallow copies. If the copy-ctor is
41 * passed another FastCopyHashMap, or clone is called on this map, the shallow
42 * copy can be performed using little more than a single array copy. In order to
43 * accomplish this, immutable objects must be used internally, so update
44 * operations result in slightly more object churn than <code>HashMap</code>.
45 * <p/>
46 * Note: It is very important to use a smaller load factor than you normally
47 * would for HashMap, since the implementation is open-addressed with linear
48 * probing. With a 50% load-factor a get is expected to return in only 2 probes.
49 * However, a 90% load-factor is expected to return in around 50 probes.
50 *
51 * @author Jason T. Greene
52 */
53class FastCopyHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
54 /**
55 * Marks null keys.
56 */
57 private static final Object NULL = new Object();
58
59 /**
60 * Serialization ID
61 */
62 private static final long serialVersionUID = 10929568968762L;
63
64 /**
65 * Same default as HashMap, must be a power of 2
66 */
67 private static final int DEFAULT_CAPACITY = 8;
68
69 /**
70 * MAX_INT - 1
71 */
72 private static final int MAXIMUM_CAPACITY = 1 << 30;
73
74 /**
75 * 67%, just like IdentityHashMap
76 */
77 private static final float DEFAULT_LOAD_FACTOR = 0.67f;
78
79 /**
80 * The open-addressed table
81 */
82 private transient Entry<K, V>[] table;
83
84 /**
85 * The current number of key-value pairs
86 */
87 private transient int size;
88
89 /**
90 * The next resize
91 */
92 private transient int threshold;
93
94 /**
95 * The user defined load factor which defines when to resize
96 */
97 private final float loadFactor;
98
99 /**
100 * Counter used to detect changes made outside of an iterator
101 */
102 private transient int modCount;
103
104 // Cached views
105 private transient KeySet keySet;
106 private transient Values values;
107 private transient EntrySet entrySet;
108
109 public FastCopyHashMap(int initialCapacity, float loadFactor) {
110 if (initialCapacity < 0)
111 throw MESSAGES.invalidTableSize();
112
113 if (initialCapacity > MAXIMUM_CAPACITY)
114 initialCapacity = MAXIMUM_CAPACITY;
115
116 if (!(loadFactor > 0F && loadFactor <= 1F))
117 throw MESSAGES.invalidLoadFactor();
118
119 this.loadFactor = loadFactor;
120 init(initialCapacity, loadFactor);
121 }
122
123 @SuppressWarnings("unchecked")
124 public FastCopyHashMap(Map<? extends K, ? extends V> map) {
125 if (map instanceof FastCopyHashMap) {
126 FastCopyHashMap<? extends K, ? extends V> fast = (FastCopyHashMap<? extends K, ? extends V>) map;
127 this.table = (Entry<K, V>[]) fast.table.clone();
128 this.loadFactor = fast.loadFactor;
129 this.size = fast.size;
130 this.threshold = fast.threshold;
131 } else {
132 this.loadFactor = DEFAULT_LOAD_FACTOR;
133 init(map.size(), this.loadFactor);
134 putAll(map);
135 }
136 }
137
138 @SuppressWarnings("unchecked")
139 private void init(int initialCapacity, float loadFactor) {
140 int c = 1;
141 while (c < initialCapacity) c <<= 1;
142 threshold = (int) (c * loadFactor);
143
144 // Include the load factor when sizing the table for the first time
145 if (initialCapacity > threshold && c < MAXIMUM_CAPACITY) {
146 c <<= 1;
147 threshold = (int) (c * loadFactor);
148 }
149
150 this.table = (Entry<K, V>[]) new Entry[c];
151 }
152
153 public FastCopyHashMap(int initialCapacity) {
154 this(initialCapacity, DEFAULT_LOAD_FACTOR);
155 }
156
157 public FastCopyHashMap() {
158 this(DEFAULT_CAPACITY);
159 }
160
161 // The normal bit spreader...
162
163 private static int hash(Object key) {
164 int h = key.hashCode();
165 h ^= (h >>> 20) ^ (h >>> 12);
166 return h ^ (h >>> 7) ^ (h >>> 4);
167 }
168
169 @SuppressWarnings("unchecked")
170 private static <K> K maskNull(K key) {
171 return key == null ? (K) NULL : key;
172 }
173
174 private static <K> K unmaskNull(K key) {
175 return key == NULL ? null : key;
176 }
177
178 private int nextIndex(int index, int length) {
179 index = (index >= length - 1) ? 0 : index + 1;
180 return index;
181 }
182
183 private static boolean eq(Object o1, Object o2) {
184 return o1 == o2 || (o1 != null && o1.equals(o2));
185 }
186
187 private static int index(int hashCode, int length) {
188 return hashCode & (length - 1);
189 }
190
191 public int size() {
192 return size;
193 }
194
195 public boolean isEmpty() {
196 return size == 0;
197 }
198
199 public V get(Object key) {
200 key = maskNull(key);
201
202 int hash = hash(key);
203 int length = table.length;
204 int index = index(hash, length);
205
206 for (int start = index; ;) {
207 Entry<K, V> e = table[index];
208 if (e == null)
209 return null;
210
211 if (e.hash == hash && eq(key, e.key))
212 return e.value;
213
214 index = nextIndex(index, length);
215 if (index == start) // Full table
216 return null;
217 }
218 }
219
220 public boolean containsKey(Object key) {
221 key = maskNull(key);
222
223 int hash = hash(key);
224 int length = table.length;
225 int index = index(hash, length);
226
227 for (int start = index; ;) {
228 Entry<K, V> e = table[index];
229 if (e == null)
230 return false;
231
232 if (e.hash == hash && eq(key, e.key))
233 return true;
234
235 index = nextIndex(index, length);
236 if (index == start) // Full table
237 return false;
238 }
239 }
240
241 public boolean containsValue(Object value) {
242 for (Entry<K, V> e : table)
243 if (e != null && eq(value, e.value))
244 return true;
245
246 return false;
247 }
248
249 public V put(K key, V value) {
250 key = maskNull(key);
251
252 Entry<K, V>[] table = this.table;
253 int hash = hash(key);
254 int length = table.length;
255 int index = index(hash, length);
256
257 for (int start = index; ;) {
258 Entry<K, V> e = table[index];
259 if (e == null)
260 break;
261
262 if (e.hash == hash && eq(key, e.key)) {
263 table[index] = new Entry<K, V>(e.key, e.hash, value);
264 return e.value;
265 }
266
267 index = nextIndex(index, length);
268 if (index == start)
269 throw MESSAGES.tableIsFull();
270 }
271
272 modCount++;
273 table[index] = new Entry<K, V>(key, hash, value);
274 if (++size >= threshold)
275 resize(length);
276
277 return null;
278 }
279
280
281 @SuppressWarnings("unchecked")
282 private void resize(int from) {
283 int newLength = from << 1;
284
285 // Can't get any bigger
286 if (newLength > MAXIMUM_CAPACITY || newLength <= from)
287 return;
288
289 Entry<K, V>[] newTable = new Entry[newLength];
290 Entry<K, V>[] old = table;
291
292 for (Entry<K, V> e : old) {
293 if (e == null)
294 continue;
295
296 int index = index(e.hash, newLength);
297 while (newTable[index] != null)
298 index = nextIndex(index, newLength);
299
300 newTable[index] = e;
301 }
302
303 threshold = (int) (loadFactor * newLength);
304 table = newTable;
305 }
306
307 public void putAll(Map<? extends K, ? extends V> map) {
308 int size = map.size();
309 if (size == 0)
310 return;
311
312 if (size > threshold) {
313 if (size > MAXIMUM_CAPACITY)
314 size = MAXIMUM_CAPACITY;
315
316 int length = table.length;
317 while (length < size) length <<= 1;
318
319 resize(length);
320 }
321
322 for (Map.Entry<? extends K, ? extends V> e : map.entrySet())
323 put(e.getKey(), e.getValue());
324 }
325
326 public V remove(Object key) {
327 key = maskNull(key);
328
329 Entry<K, V>[] table = this.table;
330 int length = table.length;
331 int hash = hash(key);
332 int start = index(hash, length);
333
334 for (int index = start; ;) {
335 Entry<K, V> e = table[index];
336 if (e == null)
337 return null;
338
339 if (e.hash == hash && eq(key, e.key)) {
340 table[index] = null;
341 relocate(index);
342 modCount++;
343 size--;
344 return e.value;
345 }
346
347 index = nextIndex(index, length);
348 if (index == start)
349 return null;
350 }
351
352
353 }
354
355 private void relocate(int start) {
356 Entry<K, V>[] table = this.table;
357 int length = table.length;
358 int current = nextIndex(start, length);
359
360 for (; ;) {
361 Entry<K, V> e = table[current];
362 if (e == null)
363 return;
364
365 // A Doug Lea variant of Knuth's Section 6.4 Algorithm R.
366 // This provides a non-recursive method of relocating
367 // entries to their optimal positions once a gap is created.
368 int prefer = index(e.hash, length);
369 if ((current < prefer && (prefer <= start || start <= current))
370 || (prefer <= start && start <= current)) {
371 table[start] = e;
372 table[current] = null;
373 start = current;
374 }
375
376 current = nextIndex(current, length);
377 }
378 }
379
380 public void clear() {
381 modCount++;
382 Entry<K, V>[] table = this.table;
383 for (int i = 0; i < table.length; i++)
384 table[i] = null;
385
386 size = 0;
387 }
388
389 @SuppressWarnings("unchecked")
390 public FastCopyHashMap<K, V> clone() {
391 try {
392 FastCopyHashMap<K, V> clone = (FastCopyHashMap<K, V>) super.clone();
393 clone.table = table.clone();
394 clone.entrySet = null;
395 clone.values = null;
396 clone.keySet = null;
397 return clone;
398 }
399 catch (CloneNotSupportedException e) {
400 // should never happen
401 throw new IllegalStateException(e);
402 }
403 }
404
405 public void printDebugStats() {
406 int optimal = 0;
407 int total = 0;
408 int totalSkew = 0;
409 int maxSkew = 0;
410 for (int i = 0; i < table.length; i++) {
411 Entry<K, V> e = table[i];
412 if (e != null) {
413
414 total++;
415 int target = index(e.hash, table.length);
416 if (i == target)
417 optimal++;
418 else {
419 int skew = Math.abs(i - target);
420 if (skew > maxSkew) maxSkew = skew;
421 totalSkew += skew;
422 }
423
424 }
425 }
426
427 System.out.println(" Size: " + size);
428 System.out.println(" Real Size: " + total);
429 System.out.println(" Optimal: " + optimal + " (" + (float) optimal * 100 / total + "%)");
430 System.out.println(" Average Distnce: " + ((float) totalSkew / (total - optimal)));
431 System.out.println(" Max Distance: " + maxSkew);
432 }
433
434 public Set<Map.Entry<K, V>> entrySet() {
435 if (entrySet == null)
436 entrySet = new EntrySet();
437
438 return entrySet;
439 }
440
441 public Set<K> keySet() {
442 if (keySet == null)
443 keySet = new KeySet();
444
445 return keySet;
446 }
447
448 public Collection<V> values() {
449 if (values == null)
450 values = new Values();
451
452 return values;
453 }
454
455 @SuppressWarnings("unchecked")
456 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
457 s.defaultReadObject();
458
459 int size = s.readInt();
460
461 init(size, loadFactor);
462
463 for (int i = 0; i < size; i++) {
464 K key = (K) s.readObject();
465 V value = (V) s.readObject();
466 putForCreate(key, value);
467 }
468
469 this.size = size;
470 }
471
472 @SuppressWarnings("unchecked")
473 private void putForCreate(K key, V value) {
474 key = maskNull(key);
475
476 Entry<K, V>[] table = this.table;
477 int hash = hash(key);
478 int length = table.length;
479 int index = index(hash, length);
480
481 Entry<K, V> e = table[index];
482 while (e != null) {
483 index = nextIndex(index, length);
484 e = table[index];
485 }
486
487 table[index] = new Entry<K, V>(key, hash, value);
488 }
489
490 private void writeObject(java.io.ObjectOutputStream s) throws IOException {
491 s.defaultWriteObject();
492 s.writeInt(size);
493
494 for (Entry<K, V> e : table) {
495 if (e != null) {
496 s.writeObject(unmaskNull(e.key));
497 s.writeObject(e.value);
498 }
499 }
500 }
501
502 private static final class Entry<K, V> {
503 final K key;
504 final int hash;
505 final V value;
506
507 Entry(K key, int hash, V value) {
508 this.key = key;
509 this.hash = hash;
510 this.value = value;
511 }
512 }
513
514 private abstract class FasyCopyHashMapIterator<E> implements Iterator<E> {
515 private int next = 0;
516 private int expectedCount = modCount;
517 private int current = -1;
518 private boolean hasNext;
519 Entry<K, V>[] table = FastCopyHashMap.this.table;
520
521 public boolean hasNext() {
522 if (hasNext == true)
523 return true;
524
525 Entry<K, V>[] table = this.table;
526 for (int i = next; i < table.length; i++) {
527 if (table[i] != null) {
528 next = i;
529 return hasNext = true;
530 }
531 }
532
533 next = table.length;
534 return false;
535 }
536
537 protected Entry<K, V> nextEntry() {
538 if (modCount != expectedCount)
539 throw new ConcurrentModificationException();
540
541 if (!hasNext && !hasNext())
542 throw new NoSuchElementException();
543
544 current = next++;
545 hasNext = false;
546
547 return table[current];
548 }
549
550 @SuppressWarnings("unchecked")
551 public void remove() {
552 if (modCount != expectedCount)
553 throw new ConcurrentModificationException();
554
555 int current = this.current;
556 int delete = current;
557
558 if (current == -1)
559 throw new IllegalStateException();
560
561 // Invalidate current (prevents multiple remove)
562 this.current = -1;
563
564 // Start were we relocate
565 next = delete;
566
567 Entry<K, V>[] table = this.table;
568 if (table != FastCopyHashMap.this.table) {
569 FastCopyHashMap.this.remove(table[delete].key);
570 table[delete] = null;
571 expectedCount = modCount;
572 return;
573 }
574
575
576 int length = table.length;
577 int i = delete;
578
579 table[delete] = null;
580 size--;
581
582 for (; ;) {
583 i = nextIndex(i, length);
584 Entry<K, V> e = table[i];
585 if (e == null)
586 break;
587
588 int prefer = index(e.hash, length);
589 if ((i < prefer && (prefer <= delete || delete <= i))
590 || (prefer <= delete && delete <= i)) {
591 // Snapshot the unseen portion of the table if we have
592 // to relocate an entry that was already seen by this iterator
593 if (i < current && current <= delete && table == FastCopyHashMap.this.table) {
594 int remaining = length - current;
595 Entry<K, V>[] newTable = (Entry<K, V>[]) new Entry[remaining];
596 System.arraycopy(table, current, newTable, 0, remaining);
597
598 // Replace iterator's table.
599 // Leave table local var pointing to the real table
600 this.table = newTable;
601 next = 0;
602 }
603
604 // Do the swap on the real table
605 table[delete] = e;
606 table[i] = null;
607 delete = i;
608 }
609 }
610 }
611 }
612
613
614 private class KeyIterator extends FasyCopyHashMapIterator<K> {
615 public K next() {
616 return unmaskNull(nextEntry().key);
617 }
618 }
619
620 private class ValueIterator extends FasyCopyHashMapIterator<V> {
621 public V next() {
622 return nextEntry().value;
623 }
624 }
625
626 private class EntryIterator extends FasyCopyHashMapIterator<Map.Entry<K, V>> {
627 private class WriteThroughEntry extends SimpleEntry<K, V> {
628 WriteThroughEntry(K key, V value) {
629 super(key, value);
630 }
631
632 public V setValue(V value) {
633 if (table != FastCopyHashMap.this.table)
634 FastCopyHashMap.this.put(getKey(), value);
635
636 return super.setValue(value);
637 }
638 }
639
640 public Map.Entry<K, V> next() {
641 Entry<K, V> e = nextEntry();
642 return new WriteThroughEntry(unmaskNull(e.key), e.value);
643 }
644
645 }
646
647 private class KeySet extends AbstractSet<K> {
648 public Iterator<K> iterator() {
649 return new KeyIterator();
650 }
651
652 public void clear() {
653 FastCopyHashMap.this.clear();
654 }
655
656 public boolean contains(Object o) {
657 return containsKey(o);
658 }
659
660 public boolean remove(Object o) {
661 int size = size();
662 FastCopyHashMap.this.remove(o);
663 return size() < size;
664 }
665
666 public int size() {
667 return FastCopyHashMap.this.size();
668 }
669 }
670
671 private class Values extends AbstractCollection<V> {
672 public Iterator<V> iterator() {
673 return new ValueIterator();
674 }
675
676 public void clear() {
677 FastCopyHashMap.this.clear();
678 }
679
680 public int size() {
681 return FastCopyHashMap.this.size();
682 }
683 }
684
685 private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
686 public Iterator<Map.Entry<K, V>> iterator() {
687 return new EntryIterator();
688 }
689
690 public boolean contains(Object o) {
691 if (!(o instanceof Map.Entry))
692 return false;
693
694 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
695 Object value = get(entry.getKey());
696 return eq(entry.getValue(), value);
697 }
698
699 public void clear() {
700 FastCopyHashMap.this.clear();
701 }
702
703 public boolean isEmpty() {
704 return FastCopyHashMap.this.isEmpty();
705 }
706
707 public int size() {
708 return FastCopyHashMap.this.size();
709 }
710 }
711
712 protected static class SimpleEntry<K, V> implements Map.Entry<K, V> {
713 private K key;
714 private V value;
715
716 SimpleEntry(K key, V value) {
717 this.key = key;
718 this.value = value;
719 }
720
721 SimpleEntry(Map.Entry<K, V> entry) {
722 this.key = entry.getKey();
723 this.value = entry.getValue();
724 }
725
726 public K getKey() {
727 return key;
728 }
729
730 public V getValue() {
731 return value;
732 }
733
734 public V setValue(V value) {
735 V old = this.value;
736 this.value = value;
737 return old;
738 }
739
740 public boolean equals(Object o) {
741 if (this == o)
742 return true;
743
744 if (!(o instanceof Map.Entry))
745 return false;
746 Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
747 return eq(key, e.getKey()) && eq(value, e.getValue());
748 }
749
750 public int hashCode() {
751 return (key == null ? 0 : hash(key)) ^
752 (value == null ? 0 : hash(value));
753 }
754
755 public String toString() {
756 return getKey() + "=" + getValue();
757 }
758 }
759}