4 package net
.kezvh
.collections
.dualmaps
;
6 import java
.io
.IOException
;
7 import java
.lang
.reflect
.Array
;
8 import java
.util
.AbstractCollection
;
9 import java
.util
.AbstractMap
;
10 import java
.util
.AbstractSet
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Collection
;
13 import java
.util
.ConcurrentModificationException
;
14 import java
.util
.HashMap
;
15 import java
.util
.Iterator
;
17 import java
.util
.NoSuchElementException
;
20 import net
.kezvh
.collections
.AbstractIterator
;
21 import net
.kezvh
.collections
.MapEntryImpl
;
22 import net
.kezvh
.collections
.Pair
;
23 import net
.kezvh
.collections
.views
.SetView
;
24 import net
.kezvh
.functional
.lambda
.L1
;
27 * Sometimes you want a map with 2 keys. While one could just use java's maps
28 * but with something like Couple<K1, K2> as the key, there's an additional
29 * opperation I often want to do that you just can't do with something like
30 * that. In particular, this operation is incredibly useful if you use this
31 * collection to map the edges of a graph, and you want to iterate through all
32 * the edges into or out of a particular node. That operation is to slice the
33 * map by 1 key or the other. This class grepped a lot of its guts out of java's
34 * hashmap implementation (is that an ip issue?). However, each entry in the
35 * table also maintains connections to other entries which share it's first or
36 * second key. This way, you've got a linked list of entries for each key along
37 * each dimension. Time complexity: get(k1, k2) this is operationally identical
38 * to getting on a hashmap, so O(1) put(k1, k2, v) hashtable put + 2 linked list
39 * head inserts, so also O(1) amortized remove(k1, k2) hashtable remove + 2
40 * doubly linked list remove, so O(1) get(k1) || get(k2) returns a list of
41 * elements backed by the map in O(1) time remove(k1) || remove(k2) O(n), where
42 * n is the number of elements in get(k1) or get(k2) respectively.
49 public class HashDualMap
<K1
, K2
, V
> implements DualMap
<K1
, K2
, V
> {
51 * The default initial capacity - MUST be a power of two.
53 static final int DEFAULT_INITIAL_CAPACITY
= 16;
56 * The maximum capacity, used if a higher value is implicitly specified by
57 * either of the constructors with arguments. MUST be a power of two <=
60 static final int MAXIMUM_CAPACITY
= 1 << 30;
63 * The load factor used when none specified in constructor.
65 static final float DEFAULT_LOAD_FACTOR
= 0.75f
;
67 private static final <K1
, K2
, V
> Entry
<K1
, K2
, V
>[] createEntries(final Entry
<K1
, K2
, V
>[] other
, final int capacity
) {
68 return HashDualMap
.createEntries(other
.getClass().getComponentType(), capacity
);
71 @SuppressWarnings("unchecked")
72 private static final <K1
, K2
, V
> Entry
<K1
, K2
, V
>[] createEntries(final Class
<?
> clazz
, final int capacity
) {
73 return (Entry
<K1
, K2
, V
>[]) Array
.newInstance(clazz
, capacity
);
76 private static final <K1
, K2
> int hash(final K1 k1
, final K2 k2
) {
84 else if (k2
== null || k1
.equals(k2
))
87 h
= k1
.hashCode() ^ k2
.hashCode();
89 h ^
= (h
>>> 20) ^
(h
>>> 12);
90 return h ^
(h
>>> 7) ^
(h
>>> 4);
93 private static final <K1
, K2
> int hash(final Pair
<K1
, K2
> entry
) {
94 return HashDualMap
.hash(entry
.get1(), entry
.get2());
97 private static final int indexFor(final int hash
, final int length
) {
98 return hash
& (length
- 1);
102 * TODO make this a libary method
109 private static final <T
> boolean objectsEqual(final T a
, final T b
) {
116 * so the complicated thing is this resides in 3 lists: 1 is the list of
117 * collisions 1 is the list of all entries matching key 1 1 is the list of
118 * all entries matching key 2
125 private static final class Entry
<K1
, K2
, V
> implements DualMap
.Entry
<K1
, K2
, V
>, Pair
<K1
, K2
> {
129 private final int hash
;
130 private Entry
<K1
, K2
, V
> nextCollision
;
131 private Entry
<K1
, K2
, V
> next1
; // next in loop w/ fixed k1
132 private Entry
<K1
, K2
, V
> next2
; // next in loop w/ fixed k2
133 private Entry
<K1
, K2
, V
> prev1
; // prev in loop w/ fixed k1
134 private Entry
<K1
, K2
, V
> prev2
; // prev in loop w/ fixed k2
137 public String
toString() {
138 return "(" + this.k1
+ ", " + this.k2
+ ") => " + this.value
;
141 public String
specialString() {
142 return "next1: " + this.next1
+ " next2: " + this.next2
+ " prev1: " + this.prev1
+ " prev2: " + this.prev2
;
145 public Entry(final int hash
, final K1 k1
, final K2 k2
, final V v
, final Entry
<K1
, K2
, V
> nextCollision
) {
149 this.nextCollision
= nextCollision
;
154 * i kind of love this function
156 * @param bimap COMMENT
158 public void removeSelf(final HashDualMap
<K1
, K2
, V
> bimap
) {
159 if (this.next1
!= null)
160 this.next1
.setPrev1(this.prev1
);
161 if (this.next2
!= null)
162 this.next2
.setPrev2(this.prev2
);
163 if (this.prev1
!= null)
164 this.prev1
.setNext1(this.next1
);
165 if (this.prev2
!= null)
166 this.prev2
.setNext2(this.next2
);
167 bimap
.k1Heads
.get(this.k1
).removing(this);
168 if (bimap
.k1Heads
.get(this.k1
).isEmpty())
169 bimap
.k1Heads
.remove(this.k1
);
170 bimap
.k2Heads
.get(this.k2
).removing(this);
171 if (bimap
.k2Heads
.get(this.k2
).isEmpty())
172 bimap
.k2Heads
.remove(this.k2
);
178 public Entry
<K1
, K2
, V
> getNext1() {
183 * @param next1 the next1 to set
186 public Entry
<K1
, K2
, V
> setNext1(final Entry
<K1
, K2
, V
> next1
) {
197 public Entry
<K1
, K2
, V
> getNext2() {
202 * @param next2 the next2 to set
205 public Entry
<K1
, K2
, V
> setNext2(final Entry
<K1
, K2
, V
> next2
) {
216 public Entry
<K1
, K2
, V
> getPrev1() {
221 * @param prev1 the prev1 to set
224 public Entry
<K1
, K2
, V
> setPrev1(final Entry
<K1
, K2
, V
> prev1
) {
235 public Entry
<K1
, K2
, V
> getPrev2() {
240 * @param prev2 the prev2 to set
243 public Entry
<K1
, K2
, V
> setPrev2(final Entry
<K1
, K2
, V
> prev2
) {
251 public boolean matchesKeys(final K1 key1
, final K2 key2
) {
252 return HashDualMap
.objectsEqual(this.k1
, key1
) && HashDualMap
.objectsEqual(this.k2
, key2
);
256 * @return the nextCollision
258 public Entry
<K1
, K2
, V
> getNextCollision() {
259 return this.nextCollision
;
263 * @param nextCollision the nextCollision to set
265 public void setNextCollision(final Entry
<K1
, K2
, V
> nextCollision
) {
266 this.nextCollision
= nextCollision
;
270 * @see java.lang.Object#hashCode()
274 public int hashCode() {
279 * @see java.lang.Object#equals(java.lang.Object)
284 public boolean equals(final Object obj
) {
289 if (this.getClass() != obj
.getClass())
291 final Entry
<?
, ?
, ?
> other
= (Entry
<?
, ?
, ?
>) obj
;
292 if (this.k1
== null) {
293 if (other
.k1
!= null)
295 } else if (!this.k1
.equals(other
.k1
))
297 if (this.k2
== null) {
298 if (other
.k2
!= null)
300 } else if (!this.k2
.equals(other
.k2
))
306 * @see net.kezvh.collections.dualmaps.DualMap.Entry#getKey1()
310 public K1
getKey1() {
315 * @see net.kezvh.collections.dualmaps.DualMap.Entry#getKey2()
319 public K2
getKey2() {
324 * @see net.kezvh.collections.dualmaps.DualMap.Entry#getValue()
328 public V
getValue() {
333 * @see net.kezvh.collections.dualmaps.DualMap.Entry#setValue(java.lang.Object)
334 * @param value COMMENT
338 public V
setValue(final V value
) {
347 * @see java.util.Map.Entry#getKey()
351 public Pair
<K1
, K2
> getKey() {
356 * @see net.kezvh.collections.Pair#get1()
365 * @see net.kezvh.collections.Pair#get2()
374 private abstract class HeadSet
extends AbstractSet
<Entry
<K1
, K2
, V
>> {
375 private int headsetSize
;
377 private Entry
<K1
, K2
, V
> head
;
379 private final class HeadSetIterator
extends AbstractIterator
<Entry
<K1
, K2
, V
>> {
380 private Entry
<K1
, K2
, V
> current
= HeadSet
.this.head
;
383 * @see net.kezvh.collections.AbstractIterator#findNext()
385 * @throws NoSuchElementException COMMENT
388 protected Entry
<K1
, K2
, V
> findNext() throws NoSuchElementException
{
389 if (this.current
== null)
390 throw new NoSuchElementException();
394 this.current
= HeadSet
.this.getNext(this.current
);
399 * @see net.kezvh.collections.AbstractIterator#remove()
402 public void remove() {
403 HashDualMap
.this.remove(HeadSet
.this.getPrev(this.current
));
407 public final void insertNewHead(final Entry
<K1
, K2
, V
> newHead
) {
408 this.setNext(newHead
, this.head
);
409 if (this.head
!= null)
410 this.setPrev(this.head
, newHead
);
415 public final void removing(final Entry
<K1
, K2
, V
> removedEntry
) {
416 if (this.head
== removedEntry
)
417 this.head
= this.getNext(removedEntry
);
421 protected abstract Entry
<K1
, K2
, V
> getNext(Entry
<K1
, K2
, V
> entry
);
423 protected abstract Entry
<K1
, K2
, V
> getPrev(Entry
<K1
, K2
, V
> entry
);
425 protected abstract Entry
<K1
, K2
, V
> setNext(Entry
<K1
, K2
, V
> entry
, Entry
<K1
, K2
, V
> next
);
427 protected abstract Entry
<K1
, K2
, V
> setPrev(Entry
<K1
, K2
, V
> entry
, Entry
<K1
, K2
, V
> prev
);
430 * @see java.util.AbstractCollection#iterator()
434 public Iterator
<Entry
<K1
, K2
, V
>> iterator() {
435 return new HeadSetIterator();
439 * @see java.util.AbstractCollection#size()
444 return this.headsetSize
;
448 * @see java.util.AbstractCollection#add(java.lang.Object)
453 public boolean add(final Entry
<K1
, K2
, V
> o
) {
454 return HashDualMap
.this.put(o
) != null;
458 * @see java.util.AbstractCollection#addAll(java.util.Collection)
463 public boolean addAll(final Collection
<?
extends Entry
<K1
, K2
, V
>> c
) {
464 return HashDualMap
.this.putAll(c
);
468 * @see java.util.AbstractCollection#clear()
471 public void clear() {
472 for (final Entry
<K1
, K2
, V
> entry
: this)
473 HashDualMap
.this.remove(entry
);
477 * @see java.util.AbstractCollection#contains(java.lang.Object)
481 @SuppressWarnings("unchecked")
483 public boolean contains(final Object o
) {
484 if (!(o
instanceof DualMap
.Entry
))
487 final DualMap
.Entry
<K1
, K2
, V
> other
= (DualMap
.Entry
<K1
, K2
, V
>) o
;
488 return HashDualMap
.this.containsKey(other
.getKey1(), other
.getKey2());
492 * @see java.util.AbstractCollection#containsAll(java.util.Collection)
497 public boolean containsAll(final Collection
<?
> c
) {
498 for (final Object entry
: c
)
499 if (!this.contains(entry
))
505 * @see java.util.AbstractCollection#isEmpty()
509 public boolean isEmpty() {
510 return this.head
== null;
514 * @see java.util.AbstractCollection#remove(java.lang.Object)
519 public boolean remove(final Object o
) {
520 return HashDualMap
.this.remove(o
) != null;
524 * @see java.util.AbstractCollection#retainAll(java.util.Collection)
529 public boolean retainAll(final Collection
<?
> c
) {
530 boolean modified
= false;
531 for (final Entry
<K1
, K2
, V
> entry
: this)
532 if (!c
.contains(entry
))
533 modified
|= HashDualMap
.this.remove(entry
) != null;
539 * @see java.util.AbstractCollection#toArray()
540 * @return TOOD COMMENT
543 public Object
[] toArray() {
544 final Entry
<K1
, K2
, V
>[] array
= HashDualMap
.createEntries(this.head
.getClass(), HashDualMap
.this.size
);
545 return this.toArray(array
);
548 @SuppressWarnings("unchecked")
550 public <T
extends Object
> T
[] toArray(final T
[] a
) {
551 final T
[] b
= a
.length
>= HashDualMap
.this.size ? a
: (T
[]) HashDualMap
.createEntries(a
.getClass().getComponentType(), HashDualMap
.this.size
);
553 for (final Entry
<K1
, K2
, V
> entry
: this)
559 private final class HeadSet1
extends HeadSet
{
561 protected Entry
<K1
, K2
, V
> getPrev(final Entry
<K1
, K2
, V
> arg0
) {
562 return arg0
.getPrev1();
566 protected Entry
<K1
, K2
, V
> getNext(final Entry
<K1
, K2
, V
> arg0
) {
567 return arg0
.getNext1();
571 protected Entry
<K1
, K2
, V
> setNext(final Entry
<K1
, K2
, V
> entry
, final Entry
<K1
, K2
, V
> next
) {
572 return entry
.setNext1(next
);
576 protected Entry
<K1
, K2
, V
> setPrev(final Entry
<K1
, K2
, V
> entry
, final Entry
<K1
, K2
, V
> prev
) {
577 return entry
.setPrev1(prev
);
581 private final class HeadSet2
extends HeadSet
{
583 protected Entry
<K1
, K2
, V
> getPrev(final Entry
<K1
, K2
, V
> arg0
) {
584 return arg0
.getPrev2();
588 protected Entry
<K1
, K2
, V
> getNext(final Entry
<K1
, K2
, V
> arg0
) {
589 return arg0
.getNext2();
593 protected Entry
<K1
, K2
, V
> setNext(final Entry
<K1
, K2
, V
> entry
, final Entry
<K1
, K2
, V
> next
) {
594 return entry
.setNext2(next
);
598 protected Entry
<K1
, K2
, V
> setPrev(final Entry
<K1
, K2
, V
> entry
, final Entry
<K1
, K2
, V
> prev
) {
599 return entry
.setPrev2(prev
);
603 private final Map
<K1
, HeadSet1
> k1Heads
= new HashMap
<K1
, HeadSet1
>();
604 private final Map
<K2
, HeadSet2
> k2Heads
= new HashMap
<K2
, HeadSet2
>();
605 private Entry
<K1
, K2
, V
>[] entries
;
607 * The number of key-value mappings contained in this identity hash map.
609 private transient int size
;
612 * The next size value at which to resize (capacity * load factor).
616 private int threshold
;
619 * The load factor for the hash table.
623 private final float loadFactor
;
626 * The number of times this HashMap has been structurally modified
627 * Structural modifications are those that change the number of mappings in
628 * the HashMap or otherwise modify its internal structure (e.g., rehash).
629 * This field is used to make iterators on Collection-views of the HashMap
630 * fail-fast. (See ConcurrentModificationException).
632 private transient volatile int modCount
;
635 * TODO refactor this so it uses AbstractIterator
640 private abstract class HashIterator
<E
> implements Iterator
<E
> {
641 Entry
<K1
, K2
, V
> next
; // next entry to return
642 int expectedModCount
; // For fast-fail
643 int index
; // current slot
644 Entry
<K1
, K2
, V
> current
; // current entry
646 public HashIterator() {
647 this.expectedModCount
= HashDualMap
.this.modCount
;
648 final Entry
<K1
, K2
, V
>[] t
= HashDualMap
.this.entries
;
650 Entry
<K1
, K2
, V
> n
= null;
651 if (HashDualMap
.this.size
!= 0)
652 while (i
> 0 && (n
= t
[--i
]) == null) {
653 // nothing TODO maybe refactor this so it's.. not so ugly
659 public boolean hasNext() {
660 return this.next
!= null;
663 protected Entry
<K1
, K2
, V
> nextEntry() {
664 if (HashDualMap
.this.modCount
!= this.expectedModCount
)
665 throw new ConcurrentModificationException();
666 final Entry
<K1
, K2
, V
> e
= this.next
;
668 throw new NoSuchElementException();
670 Entry
<K1
, K2
, V
> n
= e
.getNextCollision();
671 final Entry
<K1
, K2
, V
>[] t
= HashDualMap
.this.entries
;
673 while (n
== null && i
> 0)
677 return this.current
= e
;
680 public void remove() {
681 if (this.current
== null)
682 throw new IllegalStateException();
683 if (HashDualMap
.this.modCount
!= this.expectedModCount
)
684 throw new ConcurrentModificationException();
685 final Pair
<K1
, K2
> k
= this.current
.getKey();
687 HashDualMap
.this.removeEntryForKey(k
.get1(), k
.get2());
688 this.expectedModCount
= HashDualMap
.this.modCount
;
693 private class ValueIterator
extends HashIterator
<V
> {
695 return this.nextEntry().getValue();
699 private class KeyIterator
extends HashIterator
<Pair
<K1
, K2
>> {
700 public Pair
<K1
, K2
> next() {
701 return this.nextEntry().getKey();
705 private class EntryIterator
extends HashIterator
<Map
.Entry
<Pair
<K1
, K2
>, V
>> {
706 public Map
.Entry
<Pair
<K1
, K2
>, V
> next() {
707 return this.nextEntry();
711 // Subclass overrides these to alter behavior of views' iterator() method
712 Iterator
<Pair
<K1
, K2
>> newKeyIterator() {
713 return new KeyIterator();
716 Iterator
<V
> newValueIterator() {
717 return new ValueIterator();
720 Iterator
<Map
.Entry
<Pair
<K1
, K2
>, V
>> newEntryIterator() {
721 return new EntryIterator();
726 * Each of these fields are initialized to contain an instance of the
727 * appropriate view the first time this view is requested. The views are
728 * stateless, so there's no reason to create more than one of each.
730 private transient volatile Set
<Pair
<K1
, K2
>> keySet
= null;
731 private transient volatile Collection
<V
> values
= null;
732 private transient volatile Set
<Map
.Entry
<Pair
<K1
, K2
>, V
>> entrySet
= null;
735 * Returns a set view of the keys contained in this map. The set is backed
736 * by the map, so changes to the map are reflected in the set, and
737 * vice-versa. The set supports element removal, which removes the
738 * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
739 * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
740 * <tt>clear</tt> operations. It does not support the <tt>add</tt> or
741 * <tt>addAll</tt> operations.
743 * @return a set view of the keys contained in this map.
745 public Set
<Pair
<K1
, K2
>> keySet() {
746 final Set
<Pair
<K1
, K2
>> ks
= this.keySet
;
747 return (ks
!= null ? ks
: (this.keySet
= new KeySet()));
750 private class KeySet
extends AbstractSet
<Pair
<K1
, K2
>> {
752 public Iterator
<Pair
<K1
, K2
>> iterator() {
753 return HashDualMap
.this.newKeyIterator();
758 return HashDualMap
.this.size
;
762 public boolean contains(final Object o
) {
763 return HashDualMap
.this.containsKey(o
);
766 @SuppressWarnings("unchecked")
768 public boolean remove(final Object o
) {
769 final Pair
<K1
, K2
> other
= (Pair
<K1
, K2
>) o
;
770 return HashDualMap
.this.removeEntryForKey(other
.get1(), other
.get2()) != null;
774 public void clear() {
775 HashDualMap
.this.clear();
780 * Returns a collection view of the values contained in this map. The
781 * collection is backed by the map, so changes to the map are reflected in
782 * the collection, and vice-versa. The collection supports element removal,
783 * which removes the corresponding mapping from this map, via the
784 * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
785 * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
786 * the <tt>add</tt> or <tt>addAll</tt> operations.
788 * @return a collection view of the values contained in this map.
790 public Collection
<V
> values() {
791 final Collection
<V
> vs
= this.values
;
792 return (vs
!= null ? vs
: (this.values
= new Values()));
795 private class Values
extends AbstractCollection
<V
> {
797 public Iterator
<V
> iterator() {
798 return HashDualMap
.this.newValueIterator();
803 return HashDualMap
.this.size
;
807 public boolean contains(final Object o
) {
808 return HashDualMap
.this.containsValue(o
);
812 public void clear() {
813 HashDualMap
.this.clear();
818 * Returns a collection view of the mappings contained in this map. Each
819 * element in the returned collection is a <tt>Map.Entry</tt>. The
820 * collection is backed by the map, so changes to the map are reflected in
821 * the collection, and vice-versa. The collection supports element removal,
822 * which removes the corresponding mapping from the map, via the
823 * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
824 * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
825 * the <tt>add</tt> or <tt>addAll</tt> operations.
827 * @return a collection view of the mappings contained in this map.
830 public Set
<Map
.Entry
<Pair
<K1
, K2
>, V
>> entrySet() {
831 final Set
<Map
.Entry
<Pair
<K1
, K2
>, V
>> es
= this.entrySet
;
832 return (es
!= null ? es
: (this.entrySet
= new EntrySet()));
835 private Entry
<K1
, K2
, V
> getEntry(final K1 key1
, final K2 key2
) {
836 for (Entry
<K1
, K2
, V
> entry
= this.entries
[HashDualMap
.indexFor(HashDualMap
.hash(key1
, key2
), this.entries
.length
)]; entry
!= null; entry
= entry
.getNextCollision())
837 if (entry
.matchesKeys(key1
, key2
))
842 private class EntrySet
extends AbstractSet
<Map
.Entry
<Pair
<K1
, K2
>, V
>> {
844 public Iterator
<Map
.Entry
<Pair
<K1
, K2
>, V
>> iterator() {
845 return HashDualMap
.this.newEntryIterator();
848 @SuppressWarnings("unchecked")
850 public boolean contains(final Object o
) {
851 if (!(o
instanceof DualMap
.Entry
))
853 final DualMap
.Entry
<K1
, K2
, V
> e
= (DualMap
.Entry
<K1
, K2
, V
>) o
;
854 final Entry
<K1
, K2
, V
> candidate
= HashDualMap
.this.getEntry(e
.getKey1(), e
.getKey2());
855 return candidate
!= null && candidate
.equals(e
);
858 @SuppressWarnings("unchecked")
860 public boolean remove(final Object o
) {
861 return HashDualMap
.this.removeMapping((Map
.Entry
<Pair
<K1
, K2
>, V
>) o
) != null;
866 return HashDualMap
.this.size
;
870 public void clear() {
871 HashDualMap
.this.clear();
876 * Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,
879 * @serialData The <i>capacity</i> of the HashMap (the length of the bucket
880 * array) is emitted (int), followed by the <i>size</i> of the
881 * HashMap (the number of key-value mappings), followed by the
882 * key (Object) and value (Object) for each key-value mapping
883 * represented by the HashMap The key-value mappings are emitted
884 * in the order that they are returned by
885 * <tt>entrySet().iterator()</tt>.
887 private void writeObject(final java
.io
.ObjectOutputStream s
) throws IOException
{
888 final Iterator
<Map
.Entry
<Pair
<K1
, K2
>, V
>> i
= this.entrySet().iterator();
890 // Write out the threshold, loadfactor, and any hidden stuff
891 s
.defaultWriteObject();
893 // Write out number of buckets
894 s
.writeInt(this.entries
.length
);
896 // Write out size (number of Mappings)
897 s
.writeInt(this.size
);
899 // Write out keys and values (alternating)
900 while (i
.hasNext()) {
901 final Map
.Entry
<Pair
<K1
, K2
>, V
> e
= i
.next();
902 s
.writeObject(e
.getKey());
903 s
.writeObject(e
.getValue());
908 * allow subclasses to do initialization after deserialization
910 protected void init() {
914 private static final long serialVersionUID
= 362498820763181265L;
917 * Reconstitute the <tt>HashMap</tt> instance from a stream (i.e.,
920 @SuppressWarnings("unchecked")
921 private void readObject(final java
.io
.ObjectInputStream s
) throws IOException
, ClassNotFoundException
{
922 // Read in the threshold, loadfactor, and any hidden stuff
923 s
.defaultReadObject();
925 // Read in number of buckets and allocate the bucket array;
926 final int numBuckets
= s
.readInt();
927 this.entries
= HashDualMap
.createEntries(new Entry
<K1
, K2
, V
>(0, null, null, null, null).getClass(), numBuckets
);
929 this.init(); // Give subclass a chance to do its thing.
931 // Read in size (number of Mappings)
932 final int readSize
= s
.readInt();
934 // Read the keys and values, and put the mappings in the HashMap
935 for (int i
= 0; i
< readSize
; i
++) {
936 final K1 key1
= (K1
) s
.readObject();
937 final K2 key2
= (K2
) s
.readObject();
938 final V value
= (V
) s
.readObject();
939 this.putForCreate(key1
, key2
, value
);
943 // These methods are used when serializing HashSets
945 return this.entries
.length
;
949 return this.loadFactor
;
955 public HashDualMap() {
956 this.entries
= HashDualMap
.createEntries(new Entry
<K1
, K2
, V
>(0, null, null, null, null).getClass(), HashDualMap
.DEFAULT_INITIAL_CAPACITY
);
957 this.loadFactor
= HashDualMap
.DEFAULT_LOAD_FACTOR
;
961 * @see net.kezvh.collections.dualmaps.DualMap#clear()
964 public void clear() {
965 this.entries
= HashDualMap
.createEntries(this.entries
, HashDualMap
.DEFAULT_INITIAL_CAPACITY
);
966 this.k1Heads
.clear();
967 this.k2Heads
.clear();
971 * Special version of remove for EntrySet.
973 private Entry
<K1
, K2
, V
> removeMapping(final Map
.Entry
<Pair
<K1
, K2
>, V
> o
) {
974 if (!(o
instanceof DualMap
.Entry
))
977 final Map
.Entry
<Pair
<K1
, K2
>, V
> entry
= o
;
978 final Pair
<K1
, K2
> k
= entry
.getKey();
979 final int hash
= k
.hashCode();
980 final int i
= HashDualMap
.indexFor(hash
, this.entries
.length
);
981 Entry
<K1
, K2
, V
> prev
= this.entries
[i
];
982 Entry
<K1
, K2
, V
> e
= prev
;
985 final Entry
<K1
, K2
, V
> next
= e
.getNextCollision();
986 if (e
.hash
== hash
&& e
.equals(entry
)) {
990 this.entries
[i
] = next
;
992 prev
.setNextCollision(next
);
1004 * @see net.kezvh.collections.dualmaps.DualMap#containsKey(java.lang.Object,
1006 * @param key1 COMMENT
1007 * @param key2 COMMENT
1011 public boolean containsKey(final K1 key1
, final K2 key2
) {
1012 return this.getEntry(key1
, key2
) != null;
1016 * This method is used instead of put by constructors and pseudoconstructors
1017 * (clone, readObject). It does not resize the table, check for
1018 * comodification, etc. It calls createEntry rather than addEntry.
1020 private void putForCreate(final K1 key1
, final K2 key2
, final V value
) {
1021 final int hash
= HashDualMap
.hash(key1
, key2
);
1022 final int i
= HashDualMap
.indexFor(hash
, this.entries
.length
);
1025 * Look for preexisting entry for key. This will never happen for clone
1026 * or deserialize. It will only happen for construction if the input Map
1027 * is a sorted map whose ordering is inconsistent w/ equals.
1029 for (Entry
<K1
, K2
, V
> e
= this.entries
[i
]; e
!= null; e
= e
.nextCollision
)
1030 if (e
.hash
== hash
&& HashDualMap
.objectsEqual(key1
, e
.get1()) && HashDualMap
.objectsEqual(key2
, e
.get2())) {
1035 this.createEntry(hash
, key1
, key2
, value
, i
);
1039 * Like addEntry except that this version is used when creating entries as
1040 * part of Map construction or "pseudo-construction" (cloning,
1041 * deserialization). This version needn't worry about resizing the table.
1042 * Subclass overrides this to alter the behavior of HashMap(Map), clone, and
1045 private void createEntry(final int hash
, final K1 key1
, final K2 key2
, final V value
, final int bucketIndex
) {
1046 final Entry
<K1
, K2
, V
> e
= this.entries
[bucketIndex
];
1047 this.entries
[bucketIndex
] = new Entry
<K1
, K2
, V
>(hash
, key1
, key2
, value
, e
);
1052 * @see net.kezvh.collections.dualmaps.DualMap#entrySetOnKey1(java.lang.Object)
1053 * @param key1 COMMENT
1057 public Set
<DualMap
.Entry
<K1
, K2
, V
>> entrySetOnKey1(final K1 key1
) {
1058 return new SetView
<Entry
<K1
, K2
, V
>, DualMap
.Entry
<K1
, K2
, V
>>(this.k1Heads
.get(key1
), this.getBimapEntry
);
1061 private final L1
<DualMap
.Entry
<K1
, K2
, V
>, Entry
<K1
, K2
, V
>> getBimapEntry
= new L1
<DualMap
.Entry
<K1
, K2
, V
>, Entry
<K1
, K2
, V
>>() {
1063 public DualMap
.Entry
<K1
, K2
, V
> op(final Entry
<K1
, K2
, V
> param
) {
1068 private final L1
<K1
, Entry
<K1
, K2
, V
>> getKey1
= new L1
<K1
, Entry
<K1
, K2
, V
>>() {
1070 public K1
op(final Entry
<K1
, K2
, V
> param
) {
1071 return param
.getKey1();
1075 private final L1
<K2
, Entry
<K1
, K2
, V
>> getKey2
= new L1
<K2
, Entry
<K1
, K2
, V
>>() {
1077 public K2
op(final Entry
<K1
, K2
, V
> param
) {
1078 return param
.getKey2();
1082 private final L1
<V
, Entry
<K1
, K2
, V
>> getValue
= new L1
<V
, Entry
<K1
, K2
, V
>>() {
1084 public V
op(final Entry
<K1
, K2
, V
> param
) {
1085 return param
.getValue();
1090 * @see net.kezvh.collections.dualmaps.DualMap#entrySetOnKey2(java.lang.Object)
1091 * @param key2 COMMENT
1095 public Set
<DualMap
.Entry
<K1
, K2
, V
>> entrySetOnKey2(final K2 key2
) {
1096 return new SetView
<Entry
<K1
, K2
, V
>, DualMap
.Entry
<K1
, K2
, V
>>(this.k2Heads
.get(key2
), this.getBimapEntry
);
1100 * @see net.kezvh.collections.dualmaps.DualMap#get(java.lang.Object,
1102 * @param key1 COMMENT
1103 * @param key2 COMMENT
1107 public V
get(final K1 key1
, final K2 key2
) {
1108 if (this.containsKey(key1
, key2
))
1109 return this.getEntry(key1
, key2
).value
;
1114 * @see net.kezvh.collections.dualmaps.DualMap#isEmpty()
1118 public boolean isEmpty() {
1119 return this.size
== 0;
1123 * @see net.kezvh.collections.dualmaps.DualMap#keySetOnKey1(java.lang.Object)
1124 * @param key1 COMMENT
1128 public Set
<K2
> keySetOnKey1(final K1 key1
) {
1129 if (!this.k1Heads
.containsKey(key1
))
1131 return new SetView
<Entry
<K1
, K2
, V
>, K2
>(this.k1Heads
.get(key1
), this.getKey2
);
1135 * @see net.kezvh.collections.dualmaps.DualMap#keySetOnKey2(java.lang.Object)
1136 * @param key2 COMMENT
1140 public Set
<K1
> keySetOnKey2(final K2 key2
) {
1141 if (!this.k2Heads
.containsKey(key2
))
1143 return new SetView
<Entry
<K1
, K2
, V
>, K1
>(this.k2Heads
.get(key2
), this.getKey1
);
1147 * @see net.kezvh.collections.dualmaps.DualMap#put(java.lang.Object,
1148 * java.lang.Object, java.lang.Object)
1149 * @param key1 COMMENT
1150 * @param key2 COMMENT
1151 * @param value COMMENT
1155 public V
put(final K1 key1
, final K2 key2
, final V value
) {
1156 final Entry
<K1
, K2
, V
> entry
= this.getEntry(key1
, key2
);
1158 return entry
.setValue(value
);
1160 this.addEntry(key1
, key2
, value
);
1164 private void addEntry(final K1 key1
, final K2 key2
, final V value
) {
1165 if (this.size
++ >= this.threshold
)
1166 this.resize(this.entries
.length
<< 1);
1167 final int hash
= HashDualMap
.hash(key1
, key2
);
1168 final int index
= HashDualMap
.indexFor(hash
, this.entries
.length
);
1169 final Entry
<K1
, K2
, V
> e
= this.entries
[index
];
1170 final Entry
<K1
, K2
, V
> newEntry
= new Entry
<K1
, K2
, V
>(hash
, key1
, key2
, value
, e
);
1171 this.entries
[index
] = newEntry
;
1173 HeadSet1 key1Head
= this.k1Heads
.get(key1
);
1174 if (key1Head
== null) {
1175 key1Head
= new HeadSet1();
1176 this.k1Heads
.put(key1
, key1Head
);
1179 HeadSet2 key2Head
= this.k2Heads
.get(key2
);
1180 if (key2Head
== null) {
1181 key2Head
= new HeadSet2();
1182 this.k2Heads
.put(key2
, key2Head
);
1185 key1Head
.insertNewHead(newEntry
);
1186 key2Head
.insertNewHead(newEntry
);
1189 private void resize(final int newSize
) {
1190 final Entry
<K1
, K2
, V
>[] newTable
= HashDualMap
.createEntries(this.entries
.getClass().getComponentType(), newSize
);
1191 for (final Entry
<K1
, K2
, V
> entry
: this.entries
)
1193 newTable
[HashDualMap
.indexFor(HashDualMap
.hash(entry
), newSize
)] = entry
;
1197 * @see net.kezvh.collections.dualmaps.DualMap#putAll(net.kezvh.collections.dualmaps.DualMap)
1201 public void putAll(final DualMap
<?
extends K1
, ?
extends K2
, ?
extends V
> m
) {
1202 if (this.size
+ m
.size() > this.threshold
) {
1203 final int newSize
= Integer
.highestOneBit(this.size
+ m
.size()) << 1;
1204 this.resize(newSize
);
1206 for (final Map
.Entry
<?
extends Pair
<?
extends K1
, ?
extends K2
>, ?
extends V
> o
: m
.entrySet())
1207 this.put(o
.getKey().get1(), o
.getKey().get2(), o
.getValue());
1211 * @see net.kezvh.collections.dualmaps.DualMap#remove(java.lang.Object,
1213 * @param key1 COMMENT
1214 * @param key2 COMMENT
1218 public V
remove(final K1 key1
, final K2 key2
) {
1219 final Entry
<K1
, K2
, V
> e
= this.removeEntryForKey(key1
, key2
);
1220 return (e
== null ?
null : e
.value
);
1223 private Entry
<K1
, K2
, V
> removeEntryForKey(final K1 key1
, final K2 key2
) {
1224 final int hash
= HashDualMap
.hash(key1
, key2
);
1225 final int index
= HashDualMap
.indexFor(hash
, this.entries
.length
);
1227 Entry
<K1
, K2
, V
> prev
= this.entries
[index
];
1228 Entry
<K1
, K2
, V
> e
= prev
;
1231 final Entry
<K1
, K2
, V
> next
= e
.getNextCollision();
1232 if (e
.hash
== hash
&& e
.matchesKeys(key1
, key2
)) {
1236 this.entries
[index
] = next
;
1238 prev
.setNextCollision(next
);
1250 * @see net.kezvh.collections.dualmaps.DualMap#size()
1259 * @see net.kezvh.collections.dualmaps.DualMap#valuesOnKey1(java.lang.Object)
1260 * @param key1 COMMENT
1264 public Collection
<V
> valuesOnKey1(final K1 key1
) {
1265 return new SetView
<Entry
<K1
, K2
, V
>, V
>(this.k1Heads
.get(key1
), this.getValue
);
1269 * @see net.kezvh.collections.dualmaps.DualMap#valuesOnKey2(java.lang.Object)
1270 * @param key2 COMMENT
1274 public Collection
<V
> valuesOnKey2(final K2 key2
) {
1275 return new SetView
<Entry
<K1
, K2
, V
>, V
>(this.k2Heads
.get(key2
), this.getValue
);
1279 * @see java.util.Map#containsKey(java.lang.Object)
1280 * @param key COMMENT
1283 @SuppressWarnings("unchecked")
1285 public boolean containsKey(final Object key
) {
1286 if (!(key
instanceof Pair
))
1289 final Pair
<K1
, K2
> keys
= (Pair
<K1
, K2
>) key
;
1290 return this.containsKey(keys
.get1(), keys
.get2());
1294 * @see java.util.Map#containsValue(java.lang.Object)
1295 * @param value COMMENT
1299 public boolean containsValue(final Object value
) {
1300 for (final Entry
<K1
, K2
, V
> entry
: this.entries
)
1301 if (entry
!= null && HashDualMap
.objectsEqual(entry
.getValue(), value
))
1308 * @see java.util.Map#get(java.lang.Object)
1309 * @param key COMMENT
1312 @SuppressWarnings("unchecked")
1314 public V
get(final Object key
) {
1315 if (!(key
instanceof Pair
))
1318 final Pair
<K1
, K2
> keys
= (Pair
<K1
, K2
>) key
;
1319 return this.get(keys
.get1(), keys
.get2());
1323 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
1324 * @param key COMMENT
1325 * @param value COMMENT
1329 public V
put(final Pair
<K1
, K2
> key
, final V value
) {
1330 return this.put(key
.get1(), key
.get2(), value
);
1334 * @param entry COMMENT
1337 public V
put(final DualMap
.Entry
<K1
, K2
, V
> entry
) {
1338 return this.put(entry
.getKey1(), entry
.getKey2(), entry
.getValue());
1342 * @see java.util.Map#putAll(java.util.Map)
1346 public boolean putAll(final Collection
<?
extends DualMap
.Entry
<K1
, K2
, V
>> c
) {
1347 if (this.size
+ c
.size() > this.threshold
) {
1348 final int newSize
= Integer
.highestOneBit(this.size
+ c
.size()) << 1;
1349 this.resize(newSize
);
1351 boolean changed
= false;
1352 for (final DualMap
.Entry
<K1
, K2
, V
> o
: c
)
1353 changed
|= this.put(o
.getKey().get1(), o
.getKey().get2(), o
.getValue()) != null;
1358 * @see java.util.Map#putAll(java.util.Map)
1362 public void putAll(final Map
<?
extends Pair
<K1
, K2
>, ?
extends V
> t
) {
1363 if (this.size
+ t
.size() > this.threshold
) {
1364 final int newSize
= Integer
.highestOneBit(this.size
+ t
.size()) << 1;
1365 this.resize(newSize
);
1367 for (final Map
.Entry
<?
extends Pair
<?
extends K1
, ?
extends K2
>, ?
extends V
> o
: t
.entrySet())
1368 this.put(o
.getKey().get1(), o
.getKey().get2(), o
.getValue());
1372 * @see java.util.Map#remove(java.lang.Object)
1373 * @param key COMMENT
1376 @SuppressWarnings("unchecked")
1378 public V
remove(final Object key
) {
1379 if (!(key
instanceof Pair
))
1382 final Pair
<K1
, K2
> keys
= (Pair
<K1
, K2
>) key
;
1383 return this.remove(keys
.get1(), keys
.get2());
1386 private class EntryMap1
extends AbstractMap
<K2
, V
> {
1387 private final K1 key1
;
1389 private final Set
<Map
.Entry
<K2
, V
>> entryMapSet
;
1391 private final L1
<Map
.Entry
<K2
, V
>, HashDualMap
.Entry
<K1
, K2
, V
>> forwardMap
= new L1
<Map
.Entry
<K2
, V
>, HashDualMap
.Entry
<K1
, K2
, V
>>() {
1393 * @see net.kezvh.functional.lambda.L1#op(java.lang.Object)
1394 * @param param COMMENT
1398 public Map
.Entry
<K2
, V
> op(final HashDualMap
.Entry
<K1
, K2
, V
> param
) {
1399 return new Map
.Entry
<K2
, V
>() {
1401 * @see java.util.Map.Entry#getKey()
1405 public K2
getKey() {
1406 return param
.get2();
1410 * @see java.util.Map.Entry#getValue()
1414 public V
getValue() {
1415 return param
.getValue();
1419 public V
setValue(final V value
) {
1420 return param
.setValue(value
);
1427 * @see java.util.AbstractMap#get(java.lang.Object)
1428 * @param key COMMENT
1431 @SuppressWarnings("unchecked")
1433 public V
get(final Object key
) {
1434 return HashDualMap
.this.get(this.key1
, (K2
) key
);
1438 public V
put(final K2 key
, final V value
) {
1439 return HashDualMap
.this.put(this.key1
, key
, value
);
1443 * @see java.util.AbstractMap#remove(java.lang.Object)
1444 * @param key COMMENT
1447 @SuppressWarnings("unchecked")
1449 public V
remove(final Object key
) {
1450 return HashDualMap
.this.remove(this.key1
, (K2
) key
);
1453 public EntryMap1(final K1 key1
) {
1455 this.entryMapSet
= new SetView
<HashDualMap
.Entry
<K1
, K2
, V
>, Map
.Entry
<K2
, V
>>(HashDualMap
.this.k1Heads
.get(key1
), this.forwardMap
) {
1457 * @see java.util.AbstractCollection#add(java.lang.Object)
1462 public boolean add(final java
.util
.Map
.Entry
<K2
, V
> o
) {
1463 return HashDualMap
.this.put(key1
, o
.getKey(), o
.getValue()) != null;
1467 * @see java.util.AbstractCollection#clear()
1470 public void clear() {
1471 HashDualMap
.this.k1Heads
.get(key1
).clear();
1475 * @see java.util.AbstractCollection#contains(java.lang.Object)
1479 @SuppressWarnings("unchecked")
1481 public boolean contains(final Object o
) {
1482 if (!(o
instanceof Map
.Entry
))
1485 final Map
.Entry
<K2
, V
> other
= (Map
.Entry
<K2
, V
>) o
;
1486 return HashDualMap
.this.containsKey(key1
, other
.getKey());
1490 * @see java.util.AbstractCollection#remove(java.lang.Object)
1494 @SuppressWarnings("unchecked")
1496 public boolean remove(final Object o
) {
1497 final Map
.Entry
<K2
, V
> other
= (Map
.Entry
<K2
, V
>) o
;
1498 return HashDualMap
.this.remove(key1
, other
.getKey()) != null;
1504 * @see java.util.AbstractMap#entrySet()
1508 public Set
<java
.util
.Map
.Entry
<K2
, V
>> entrySet() {
1509 return this.entryMapSet
;
1514 * @see net.kezvh.collections.dualmaps.DualMap#entryMapOnKey1(java.lang.Object)
1515 * @param key1 COMMENT
1519 public Map
<K2
, V
> entryMapOnKey1(final K1 key1
) {
1520 if (this.k1Heads
.containsKey(key1
))
1521 return new EntryMap1(key1
);
1525 private class EntryMap2
extends AbstractMap
<K1
, V
> {
1526 private final K2 key2
;
1527 private final Set
<Map
.Entry
<K1
, V
>> entryMapSet
;
1529 private final L1
<Map
.Entry
<K1
, V
>, HashDualMap
.Entry
<K1
, K2
, V
>> forwardMap
= new L1
<Map
.Entry
<K1
, V
>, HashDualMap
.Entry
<K1
, K2
, V
>>() {
1531 * @see net.kezvh.functional.lambda.L1#op(java.lang.Object)
1532 * @param param COMMENT
1536 public Map
.Entry
<K1
, V
> op(final HashDualMap
.Entry
<K1
, K2
, V
> param
) {
1537 return new Map
.Entry
<K1
, V
>() {
1539 * @see java.util.Map.Entry#getKey()
1543 public K1
getKey() {
1544 return param
.get1();
1548 * @see java.util.Map.Entry#getValue()
1552 public V
getValue() {
1553 return param
.getValue();
1557 public V
setValue(final V value
) {
1558 return param
.setValue(value
);
1564 public EntryMap2(final K2 key2
) {
1566 this.entryMapSet
= new SetView
<HashDualMap
.Entry
<K1
, K2
, V
>, Map
.Entry
<K1
, V
>>(HashDualMap
.this.k2Heads
.get(key2
), this.forwardMap
) {
1568 * @see java.util.AbstractCollection#add(java.lang.Object)
1573 public boolean add(final java
.util
.Map
.Entry
<K1
, V
> o
) {
1574 return HashDualMap
.this.put(o
.getKey(), key2
, o
.getValue()) != null;
1578 * @see java.util.AbstractCollection#clear()
1581 public void clear() {
1582 HashDualMap
.this.k2Heads
.get(key2
).clear();
1586 * @see java.util.AbstractCollection#contains(java.lang.Object)
1590 @SuppressWarnings("unchecked")
1592 public boolean contains(final Object o
) {
1593 if (!(o
instanceof Map
.Entry
))
1596 final Map
.Entry
<K1
, V
> other
= (Map
.Entry
<K1
, V
>) o
;
1597 return HashDualMap
.this.containsKey(other
.getKey(), key2
);
1601 * @see java.util.AbstractCollection#remove(java.lang.Object)
1605 @SuppressWarnings("unchecked")
1607 public boolean remove(final Object o
) {
1608 final Map
.Entry
<K1
, V
> other
= (Map
.Entry
<K1
, V
>) o
;
1609 return HashDualMap
.this.remove(other
.getKey(), key2
) != null;
1615 * @see java.util.AbstractMap#entrySet()
1619 public Set
<java
.util
.Map
.Entry
<K1
, V
>> entrySet() {
1620 return this.entryMapSet
;
1624 * @see java.util.AbstractMap#get(java.lang.Object)
1625 * @param key COMMENT
1628 @SuppressWarnings("unchecked")
1630 public V
get(final Object key
) {
1631 return HashDualMap
.this.get((K1
) key
, this.key2
);
1635 public V
put(final K1 key
, final V value
) {
1636 return HashDualMap
.this.put(key
, this.key2
, value
);
1640 * @see java.util.AbstractMap#remove(java.lang.Object)
1641 * @param key COMMENT
1644 @SuppressWarnings("unchecked")
1646 public V
remove(final Object key
) {
1647 return HashDualMap
.this.remove((K1
) key
, this.key2
);
1652 * @see net.kezvh.collections.dualmaps.DualMap#entryMapOnKey2(java.lang.Object)
1653 * @param key2 COMMENT
1657 public Map
<K1
, V
> entryMapOnKey2(final K2 key2
) {
1658 if (this.k2Heads
.containsKey(key2
))
1659 return new EntryMap2(key2
);
1664 public Collection
<Map
.Entry
<K2
, V
>> get1(final K1 key1
) {
1665 if (this.k1Heads
.containsKey(key1
))
1666 return this.entryMapOnKey1(key1
).entrySet();
1671 public Collection
<Map
.Entry
<K1
, V
>> get2(final K2 key2
) {
1672 if (this.k2Heads
.containsKey(key2
))
1673 return this.entryMapOnKey2(key2
).entrySet();
1678 public Collection
<Map
.Entry
<K2
, V
>> remove1(final K1 key1
) {
1679 if (!this.k1Heads
.containsKey(key1
))
1681 final Collection
<Map
.Entry
<K2
, V
>> removedEntries
= new ArrayList
<Map
.Entry
<K2
, V
>>(this.k1Heads
.get(key1
).size());
1683 for (final Entry
<K1
, K2
, V
> entry
: this.k1Heads
.get(key1
))
1684 removedEntries
.add(new MapEntryImpl
<K2
, V
>(entry
.get2(), entry
.getValue()));
1686 for (final Map
.Entry
<K2
, V
> removedEntry
: removedEntries
)
1687 this.remove(key1
, removedEntry
.getKey());
1689 return removedEntries
;
1693 public Collection
<Map
.Entry
<K1
, V
>> remove2(final K2 key2
) {
1694 if (!this.k2Heads
.containsKey(key2
))
1697 final Collection
<Map
.Entry
<K1
, V
>> removedEntries
= new ArrayList
<Map
.Entry
<K1
, V
>>(this.k2Heads
.get(key2
).size());
1699 for (final Entry
<K1
, K2
, V
> entry
: this.k2Heads
.get(key2
))
1700 removedEntries
.add(new MapEntryImpl
<K1
, V
>(entry
.get1(), entry
.getValue()));
1702 for (final Map
.Entry
<K1
, V
> removedEntry
: removedEntries
)
1703 this.remove(removedEntry
.getKey(), key2
);
1705 return removedEntries
;
1708 public boolean containsKey1(final K1 key1
) {
1709 return this.k1Heads
.containsKey(key1
);
1712 public boolean containsKey2(final K2 key2
) {
1713 return this.k2Heads
.containsKey(key2
);