2 * Copyright 2004-2010 Brian S O'Neill
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
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.cojen.util;
19 import java.lang.ref.Reference;
20 import java.lang.ref.ReferenceQueue;
21 import java.lang.ref.WeakReference;
22 import java.util.AbstractCollection;
23 import java.util.AbstractMap;
24 import java.util.AbstractSet;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.ConcurrentModificationException;
28 import java.util.Iterator;
30 import java.util.NoSuchElementException;
34 * WeakIdentityMap is like WeakHashMap, except it uses a key's identity
35 * hashcode and equals methods. WeakIdentityMap is not thread-safe and must be
36 * wrapped with Collections.synchronizedMap to be made thread-safe.
38 * The documentation for WeakHashMap states that it is intended primarily for
39 * use with key objects whose equals methods test for object identity using the
40 * == operator. Because WeakIdentityMap strictly follows this behavior, it is
41 * better suited for this purpose.
43 * Note: Weakly referenced entries may be automatically removed during
44 * either accessor or mutator operations, possibly causing a concurrent
45 * modification to be detected. Therefore, even if multiple threads are only
46 * accessing this map, be sure to synchronize this map first. Also, do not
47 * rely on the value returned by size() when using an iterator from this map.
48 * The iterators may return less entries than the amount reported by size().
50 * @author Brian S O'Neill
52 @SuppressWarnings({ "unchecked", "rawtypes" })
53 public class WeakIdentityMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable {
56 static final int KEYS = 0;
57 static final int VALUES = 1;
58 static final int ENTRIES = 2;
61 * Converts a collection to string, supporting collections that contain
64 static String toString(Collection c) {
68 StringBuffer buf = new StringBuffer(32 * c.size());
71 Iterator it = c.iterator();
72 boolean hasNext = it.hasNext();
74 Object obj = it.next();
75 buf.append(obj == c ? "(this Collection)" : obj);
76 if (hasNext = it.hasNext()) {
81 return buf.toString();
85 * Converts a map to string, supporting maps that contain self references
87 static String toString(Map m) {
91 StringBuffer buf = new StringBuffer(32 * m.size());
94 Iterator it = m.entrySet().iterator();
95 boolean hasNext = it.hasNext();
97 Map.Entry entry = (Map.Entry)it.next();
98 Object key = entry.getKey();
99 Object value = entry.getValue();
100 buf.append(key == m ? "(this Map)" : key)
102 .append(value == m ? "(this Map)" : value);
104 hasNext = it.hasNext();
106 buf.append(',').append(' ');
111 return buf.toString();
114 private transient Entry<K, V>[] table;
115 private transient int count;
116 private int threshold;
117 private final float loadFactor;
118 private final ReferenceQueue<K> queue;
119 private transient volatile int modCount;
123 private transient Set<K> keySet;
124 private transient Set<Map.Entry<K, V>> entrySet;
125 private transient Collection<V> values;
127 public WeakIdentityMap(int initialCapacity, float loadFactor) {
128 if (initialCapacity <= 0) {
129 throw new IllegalArgumentException("Initial capacity must be greater than 0");
131 if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
132 throw new IllegalArgumentException("Load factor must be greater than 0");
134 this.loadFactor = loadFactor;
135 this.table = new Entry[initialCapacity];
136 this.threshold = (int)(initialCapacity * loadFactor);
137 this.queue = new ReferenceQueue();
140 public WeakIdentityMap(int initialCapacity) {
141 this(initialCapacity, 0.75f);
144 public WeakIdentityMap() {
148 public WeakIdentityMap(Map<? extends K, ? extends V> t) {
149 this(Math.max(2 * t.size(), 11), 0.75f);
154 // Cleanup right before, to report a more accurate size.
159 public boolean isEmpty() {
160 return this.count == 0;
163 public boolean containsValue(Object value) {
164 Entry[] tab = this.table;
167 for (int i = tab.length ; i-- > 0 ;) {
168 for (Entry e = tab[i], prev = null; e != null; e = e.next) {
169 if (e.get() == null) {
170 // Clean up after a cleared Reference.
178 } else if (e.value == null) {
186 for (int i = tab.length ; i-- > 0 ;) {
187 for (Entry e = tab[i], prev = null; e != null; e = e.next) {
188 if (e.get() == null) {
189 // Clean up after a cleared Reference.
197 } else if (value.equals(e.value)) {
209 public boolean containsKey(Object key) {
211 key = ReferencedValueHashMap.NULL;
214 Entry[] tab = this.table;
215 int hash = System.identityHashCode(key);
216 int index = (hash & 0x7fffffff) % tab.length;
218 for (Entry e = tab[index], prev = null; e != null; e = e.next) {
219 Object entryKey = e.get();
221 if (entryKey == null) {
222 // Clean up after a cleared Reference.
230 } else if (e.hash == hash && key == entryKey) {
240 public V get(Object key) {
242 key = ReferencedValueHashMap.NULL;
245 Entry<K, V>[] tab = this.table;
246 int hash = System.identityHashCode(key);
247 int index = (hash & 0x7fffffff) % tab.length;
249 for (Entry<K, V> e = tab[index], prev = null; e != null; e = e.next) {
250 Object entryKey = e.get();
252 if (entryKey == null) {
253 // Clean up after a cleared Reference.
261 } else if (e.hash == hash && key == entryKey) {
271 private void cleanup() {
272 // Cleanup after cleared References.
273 Entry[] tab = this.table;
274 ReferenceQueue queue = this.queue;
276 while ((ref = queue.poll()) != null) {
277 // Since buckets are single-linked, traverse entire list and
278 // cleanup all cleared references in it.
279 int index = (((Entry) ref).hash & 0x7fffffff) % tab.length;
280 for (Entry e = tab[index], prev = null; e != null; e = e.next) {
281 if (e.get() == null) {
296 private void rehash() {
297 int oldCapacity = this.table.length;
298 Entry[] oldMap = this.table;
300 int newCapacity = oldCapacity * 2 + 1;
301 if (newCapacity <= 0) {
303 if ((newCapacity = Integer.MAX_VALUE) == oldCapacity) {
307 Entry[] newMap = new Entry[newCapacity];
310 this.threshold = (int)(newCapacity * this.loadFactor);
313 for (int i = oldCapacity ; i-- > 0 ;) {
314 for (Entry old = oldMap[i] ; old != null ; ) {
318 // Only copy entry if its key hasn't been cleared.
319 if (e.get() == null) {
322 int index = (e.hash & 0x7fffffff) % newCapacity;
323 e.next = newMap[index];
330 public V put(K key, V value) {
332 key = (K) ReferencedValueHashMap.NULL;
337 // Make sure the key is not already in the WeakIdentityMap.
338 Entry[] tab = this.table;
339 int hash = System.identityHashCode(key);
340 int index = (hash & 0x7fffffff) % tab.length;
342 for (Entry e = tab[index], prev = null; e != null; e = e.next) {
343 Object entryKey = e.get();
345 if (entryKey == null) {
346 // Clean up after a cleared Reference.
354 } else if (e.hash == hash && key == entryKey) {
355 Object old = e.value;
365 if (this.count >= this.threshold) {
366 // Rehash the table if the threshold is still exceeded.
369 index = (hash & 0x7fffffff) % tab.length;
372 // Creates the new entry.
373 Entry e = new Entry(hash, key, this.queue, value, tab[index]);
379 public V remove(Object key) {
381 key = ReferencedValueHashMap.NULL;
384 Entry<K, V>[] tab = this.table;
385 int hash = System.identityHashCode(key);
386 int index = (hash & 0x7fffffff) % tab.length;
388 for (Entry<K, V> e = tab[index], prev = null; e != null; e = e.next) {
389 Object entryKey = e.get();
391 if (entryKey == null) {
392 // Clean up after a cleared Reference.
400 } else if (e.hash == hash && key == entryKey) {
409 V oldValue = e.value;
420 public void putAll(Map<? extends K, ? extends V> t) {
421 Iterator i = t.entrySet().iterator();
422 while (i.hasNext()) {
423 Map.Entry e = (Map.Entry) i.next();
424 put((K) e.getKey(), (V) e.getValue());
428 public void clear() {
429 Entry[] tab = this.table;
431 for (int index = tab.length; --index >= 0; ) {
437 public Object clone() {
439 WeakIdentityMap t = (WeakIdentityMap)super.clone();
440 t.table = new Entry[this.table.length];
441 for (int i = this.table.length ; i-- > 0 ; ) {
442 t.table[i] = (this.table[i] != null)
443 ? (Entry)this.table[i].copy(this.queue) : null;
451 catch (CloneNotSupportedException e) {
452 // this shouldn't happen, since we are Cloneable
453 throw new InternalError();
457 public Set<K> keySet() {
458 if (this.keySet == null) {
459 this.keySet = new AbstractSet<K>() {
460 public Iterator iterator() {
461 return createHashIterator(KEYS);
464 return WeakIdentityMap.this.count;
466 public boolean contains(Object o) {
467 return containsKey(o);
469 public boolean remove(Object o) {
470 return o == null ? false : WeakIdentityMap.this.remove(o) == o;
472 public void clear() {
473 WeakIdentityMap.this.clear();
475 public String toString() {
476 return WeakIdentityMap.toString(this);
483 public Collection<V> values() {
484 if (this.values==null) {
485 this.values = new AbstractCollection<V>() {
486 public Iterator<V> iterator() {
487 return createHashIterator(VALUES);
490 return WeakIdentityMap.this.count;
492 public boolean contains(Object o) {
493 return containsValue(o);
495 public void clear() {
496 WeakIdentityMap.this.clear();
498 public String toString() {
499 return WeakIdentityMap.toString(this);
506 public Set<Map.Entry<K, V>> entrySet() {
507 if (this.entrySet==null) {
508 this.entrySet = new AbstractSet<Map.Entry<K, V>>() {
509 public Iterator<Map.Entry<K, V>> iterator() {
510 return createHashIterator(ENTRIES);
513 public boolean contains(Object o) {
514 if (!(o instanceof Map.Entry)) {
517 Map.Entry entry = (Map.Entry)o;
518 Object key = entry.getKey();
520 Entry[] tab = WeakIdentityMap.this.table;
521 int hash = System.identityHashCode(key);
522 int index = (hash & 0x7fffffff) % tab.length;
524 for (Entry e = tab[index], prev = null; e != null; e = e.next) {
525 Object entryKey = e.get();
527 if (entryKey == null) {
528 // Clean up after a cleared Reference.
529 WeakIdentityMap.this.modCount++;
535 WeakIdentityMap.this.count--;
536 } else if (e.hash == hash && e.equals(entry)) {
546 public boolean remove(Object o) {
547 if (!(o instanceof Map.Entry)) {
550 Map.Entry entry = (Map.Entry)o;
551 Object key = entry.getKey();
552 Entry[] tab = WeakIdentityMap.this.table;
553 int hash = System.identityHashCode(key);
554 int index = (hash & 0x7fffffff) % tab.length;
556 for (Entry e = tab[index], prev = null; e != null; e = e.next) {
557 if (e.get() == null) {
558 // Clean up after a cleared Reference.
559 WeakIdentityMap.this.modCount++;
565 WeakIdentityMap.this.count--;
566 } else if (e.hash == hash && e.equals(entry)) {
567 WeakIdentityMap.this.modCount++;
573 WeakIdentityMap.this.count--;
585 return WeakIdentityMap.this.count;
588 public void clear() {
589 WeakIdentityMap.this.clear();
592 public String toString() {
593 return WeakIdentityMap.toString(this);
598 return this.entrySet;
602 * Gets the map as a String.
604 * @return a string version of the map
606 public String toString() {
607 return toString(this);
610 private Iterator createHashIterator(int type) {
611 if (this.count == 0) {
612 return Collections.EMPTY_SET.iterator();
614 return new HashIterator(type);
619 * WeakIdentityMap collision list entry.
621 private static class Entry<K, V> extends WeakReference<K> implements Map.Entry<K, V> {
626 Entry(int hash, K key, ReferenceQueue<K> queue, V value, Entry<K, V> next) {
633 public void clear() {
634 // Do nothing if reference is explicity cleared. This prevents
635 // backdoor modification of map entries.
639 K key = Entry.this.get();
640 return key == ReferencedValueHashMap.NULL ? null : key;
643 public V getValue() {
647 public V setValue(V value) {
648 V oldValue = this.value;
653 public boolean equals(Object obj) {
654 if (!(obj instanceof Map.Entry)) {
657 return equals((Map.Entry)obj);
660 boolean equals(Map.Entry<K, V> e) {
661 Object thisKey = get();
662 if (thisKey == null) {
664 } else if (thisKey == ReferencedValueHashMap.NULL) {
667 return (thisKey == e.getKey()) &&
668 (this.value == null ? e.getValue() == null : this.value.equals(e.getValue()));
671 public int hashCode() {
672 return this.hash ^ (this.value == null ? 0 : this.value.hashCode());
675 public String toString() {
676 return getKey() + "=" + this.value;
679 protected Object copy(ReferenceQueue queue) {
680 return new Entry(this.hash, get(), queue, this.value,
681 (this.next == null ? null : (Entry)this.next.copy(queue)));
685 private class HashIterator implements Iterator {
686 private final int type;
687 private final Entry[] table;
691 @SuppressWarnings("unused")
692 // To ensure that the iterator doesn't return cleared entries, keep a
693 // hard reference to the key. Its existence will prevent the weak
694 // key from being cleared.
701 * The modCount value that the iterator believes that the backing
702 * List should have. If this expectation is violated, the iterator
703 * has detected concurrent modification.
705 private int expectedModCount = WeakIdentityMap.this.modCount;
707 HashIterator(int type) {
708 this.table = WeakIdentityMap.this.table;
710 this.index = table.length;
713 public boolean hasNext() {
714 while (this.entry == null || (this.entryKey = this.entry.get()) == null) {
715 if (this.entry != null) {
716 // Clean up after a cleared Reference.
718 this.entry = this.entry.next;
721 if (this.index <= 0) {
725 this.entry = this.table[--this.index];
733 public Object next() {
734 if (WeakIdentityMap.this.modCount != this.expectedModCount) {
735 throw new ConcurrentModificationException();
739 throw new NoSuchElementException();
742 this.last = this.entry;
743 this.entry = this.entry.next;
745 return this.type == KEYS ? this.last.getKey() :
746 (this.type == VALUES ? this.last.getValue() : this.last);
749 public void remove() {
750 if (this.last == null) {
751 throw new IllegalStateException();
753 if (WeakIdentityMap.this.modCount != this.expectedModCount) {
754 throw new ConcurrentModificationException();
760 private void remove(Entry toRemove) {
761 Entry[] tab = this.table;
762 int index = (toRemove.hash & 0x7fffffff) % tab.length;
764 for (Entry e = tab[index], prev = null; e != null; e = e.next) {
766 WeakIdentityMap.this.modCount++;
773 WeakIdentityMap.this.count--;
780 throw new ConcurrentModificationException();
783 public String toString() {
784 if (this.last != null) {
785 return "Iterator[" + this.last + ']';