diff --git a/src/java/org/apache/commons/collections/StaticBucketMap.java b/src/java/org/apache/commons/collections/StaticBucketMap.java
index f10c042e6..9aa19a624 100644
--- a/src/java/org/apache/commons/collections/StaticBucketMap.java
+++ b/src/java/org/apache/commons/collections/StaticBucketMap.java
@@ -1,7 +1,7 @@
/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/StaticBucketMap.java,v 1.2 2002/07/10 03:35:32 mas Exp $
- * $Revision: 1.2 $
- * $Date: 2002/07/10 03:35:32 $
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/StaticBucketMap.java,v 1.3 2002/08/15 03:22:29 pjack Exp $
+ * $Revision: 1.3 $
+ * $Date: 2002/08/15 03:22:29 $
*
* ====================================================================
*
@@ -60,36 +60,91 @@
*/
package org.apache.commons.collections;
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
+import java.util.NoSuchElementException;
/**
* A StaticBucketMap is an efficient, thread-safe implementation of
* java.util.Map
that performs well in in a highly
* thread-contentious environment. The map supports very efficient
- * get
, put
, contains
, and
- * remove
operations, assuming (approximate) uniform hashing and
- * that the number of entries does not exceed the size of the map. If the
- * number of entries exceeds the size of the map or if the hashcodes of the
+ * {@link #get(Object) get}, {@link #put(Object,Object) put},
+ * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey}
+ * operations, assuming (approximate) uniform hashing and
+ * that the number of entries does not exceed the number of buckets. If the
+ * number of entries exceeds the number of buckets or if the hashcodes of the
* objects are not uniformly distributed, these operations have a worst case
* scenario that is proportional to the number of elements in the map
- * (O(n)).
+ * (O(n)).
+ * + * Each bucket in the hash table has its own monitor, so two threads can + * safely operate on the map at the same time, often without incurring any + * monitor contention. This means that you don't have to wrap instances + * of this class with {@link java.util.Collections#synchronizedMap(Map)}; + * instances are already thread-safe. Unfortunately, however, this means + * that this map implementation behaves in ways you may find disconcerting. + * Bulk operations, such as {@link #putAll(Map) putAll} or the + * {@link Collection#retainAll(Collection) retainAll} operation in collection + * views, are not atomic. If two threads are simultaneously + * executing + * + *
+ * staticBucketMapInstance.putAll(map); + *+ * + * and + * + *
+ * staticBucketMapInstance.entrySet().removeAll(map.entrySet()); + *+ * + * then the results are generally random. Those two statement could cancel + * each other out, leaving
staticBucketMapInstance
essentially
+ * unchanged, or they could leave some random subset of map
in
+ * staticBucketMapInstance
.+ * + * Also, much like an encyclopedia, the results of {@link #size()} and + * {@link #isEmpty()} are out-of-date as soon as they are produced.
+ * + * The iterators returned by the collection views of this class are not + * fail-fast. They will never raise a + * {@link java.util.ConcurrentModificationException}. Keys and values + * added to the map after the iterator is created do not necessarily appear + * during iteration. Similarly, the iterator does not necessarily fail to + * return keys and values that were removed after the iterator was created.
+ * + * Finally, unlike {@link java.util.HashMap}-style implementations, this + * class never rehashes the map. The number of buckets is fixed + * at construction time and never altered. Performance may degrade if + * you do not allocate enough buckets upfront.
+ * + * The {@link #atomic(Runnable)} method is provided to allow atomic iterations + * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic} + * will basically result in a map that's slower than an ordinary synchronized + * {@link java.util.HashMap}. + * + * Use this class if you do not require reliable bulk operations and + * iterations, or if you can make your own guarantees about how bulk + * operations will affect the map.
* * @author Berin Loritsch * @author Gerhard Froehlich * @author Michael A. Smith - * @version CVS $Revision: 1.2 $ $Date: 2002/07/10 03:35:32 $ + * @author Paul Jack + * @version CVS $Revision: 1.3 $ $Date: 2002/08/15 03:22:29 $ * @since Avalon 4.0 */ public final class StaticBucketMap implements Map { private static final int DEFAULT_BUCKETS = 255; - private final Node[] m_buckets; - private final Object[] m_locks; + private Node[] m_buckets; + private Lock[] m_locks; /** * Initializes the map with the default number of buckets (255). @@ -106,6 +161,8 @@ public final class StaticBucketMap implements Map * chances for thread contention. The fewer buckets, the more chances for * thread contention. The more buckets the fewer chances for thread * contention. + * + * @param numBuckets the number of buckets for this map */ public StaticBucketMap( int numBuckets ) { @@ -118,11 +175,11 @@ public final class StaticBucketMap implements Map } m_buckets = new Node[ size ]; - m_locks = new Object[ size ]; + m_locks = new Lock[ size ]; for( int i = 0; i < size; i++ ) { - m_locks[ i ] = new Object(); + m_locks[ i ] = new Lock(); } } @@ -142,34 +199,23 @@ public final class StaticBucketMap implements Map private final int getHash( Object key ) { if( key == null ) return 0; - final int hash = key.hashCode() % m_buckets.length; + int hash = key.hashCode(); + hash += ~(hash << 15); + hash ^= (hash >>> 10); + hash += (hash << 3); + hash ^= (hash >>> 6); + hash += ~(hash << 11); + hash ^= (hash >>> 16); + hash %= m_buckets.length; return ( hash < 0 ) ? hash * -1 : hash; } /** - * Obtain a Set for the keys. This operation crosses bucket boundaries, - * so it is less efficient, and greatly increases the chance for thread - * contention. + * Returns a set view of this map's keys. */ public Set keySet() { - Set keySet = new HashSet(); - - for( int i = 0; i < m_buckets.length; i++ ) - { - synchronized( m_locks[ i ] ) - { - Node n = m_buckets[ i ]; - - while( n != null ) - { - keySet.add( n.key ); - n = n.next; - } - } - } - - return keySet; + return new KeySet(); } /** @@ -181,16 +227,7 @@ public final class StaticBucketMap implements Map for( int i = 0; i < m_buckets.length; i++ ) { - synchronized( m_locks[ i ] ) - { - Node n = m_buckets[ i ]; - - while( n != null ) - { - cnt++; - n = n.next; - } - } + cnt += m_locks[i].size; } return cnt; @@ -213,6 +250,7 @@ public final class StaticBucketMap implements Map n.key = key; n.value = value; m_buckets[ hash ] = n; + m_locks[hash].size++; return null; } @@ -237,6 +275,7 @@ public final class StaticBucketMap implements Map newNode.key = key; newNode.value = value; n.next = newNode; + m_locks[hash].size++; } return null; @@ -328,23 +367,7 @@ public final class StaticBucketMap implements Map */ public Collection values() { - ArrayList values = new ArrayList(); - - for( int i = 0; i < m_buckets.length; i++ ) - { - synchronized( m_locks[ i ] ) - { - Node n = m_buckets[ i ]; - - while( n != null ) - { - values.add( n.value ); - n = n.next; - } - } - } - - return values; + return new Values(); } /** @@ -354,23 +377,7 @@ public final class StaticBucketMap implements Map */ public Set entrySet() { - Set entrySet = new HashSet(); - - for( int i = 0; i < m_buckets.length; i++ ) - { - synchronized( m_locks[ i ] ) - { - Node n = m_buckets[ i ]; - - while( n != null ) - { - entrySet.add( n ); - n = n.next; - } - } - } - - return entrySet; + return new EntrySet(); } /** @@ -414,7 +421,7 @@ public final class StaticBucketMap implements Map // Set the next node of the previous node to be the node after this one. prev.next = n.next; } - + m_locks[hash].size--; return n.value; } @@ -431,15 +438,7 @@ public final class StaticBucketMap implements Map */ public final boolean isEmpty() { - for( int i = 0; i < m_buckets.length; i++ ) - { - if( m_buckets[ i ] != null ) - { - return false; - } - } - - return true; + return size() == 0; } /** @@ -449,10 +448,21 @@ public final class StaticBucketMap implements Map { for( int i = 0; i < m_buckets.length; i++ ) { - m_buckets[ i ] = null; + Lock lock = m_locks[i]; + synchronized (lock) { + m_buckets[ i ] = null; + lock.size = 0; + } } } + /** + * Returns true if the given object is a map with the same mappings + * as this map. + * + * @return true if the given object is the a map with same mappings + * as this map + */ public final boolean equals( Object obj ) { if( obj == null ) return false; @@ -465,6 +475,11 @@ public final class StaticBucketMap implements Map return entrySet().equals(other.entrySet()); } + /** + * Returns a hash code for this map. + * + * @return a hash code for this map + */ public final int hashCode() { int hashCode = 0; @@ -532,4 +547,214 @@ public final class StaticBucketMap implements Map return retVal; } } + + private final static class Lock { + + public int size; + + } + + + private class EntryIterator implements Iterator { + + private ArrayList current = new ArrayList(); + private int bucket; + private Map.Entry last; + + + public boolean hasNext() { + if (current.size() > 0) return true; + while (bucket < m_buckets.length) { + synchronized (m_locks[bucket]) { + Node n = m_buckets[bucket]; + while (n != null) { + current.add(n); + n = n.next; + } + bucket++; + if (current.size() > 0) return true; + } + } + return false; + } + + protected Map.Entry nextEntry() { + if (!hasNext()) throw new NoSuchElementException(); + last = (Map.Entry)current.remove(current.size() - 1); + return last; + } + + public Object next() { + return nextEntry(); + } + + public void remove() { + if (last == null) throw new IllegalStateException(); + StaticBucketMap.this.remove(last.getKey()); + last = null; + } + + } + + private class ValueIterator extends EntryIterator { + + public Object next() { + return nextEntry().getValue(); + } + + } + + private class KeyIterator extends EntryIterator { + + public Object next() { + return nextEntry().getKey(); + } + + } + + private class EntrySet extends AbstractSet { + + public int size() { + return StaticBucketMap.this.size(); + } + + public void clear() { + StaticBucketMap.this.clear(); + } + + public Iterator iterator() { + return new EntryIterator(); + } + + public boolean contains(Object o) { + Map.Entry entry = (Map.Entry)o; + int hash = getHash(entry.getKey()); + synchronized (m_locks[hash]) { + for (Node n = m_buckets[hash]; n != null; n = n.next) { + if (n.equals(entry)) return true; + } + } + return false; + } + + public boolean remove(Object o) { + Map.Entry entry = (Map.Entry)o; + int hash = getHash(entry.getKey()); + synchronized (m_locks[hash]) { + for (Node n = m_buckets[hash]; n != null; n = n.next) { + if (n.equals(entry)) { + StaticBucketMap.this.remove(n.getKey()); + return true; + } + } + } + return false; + } + + } + + + private class KeySet extends AbstractSet { + + public int size() { + return StaticBucketMap.this.size(); + } + + public void clear() { + StaticBucketMap.this.clear(); + } + + public Iterator iterator() { + return new KeyIterator(); + } + + public boolean contains(Object o) { + return StaticBucketMap.this.containsKey(o); + } + + public boolean remove(Object o) { + int hash = getHash(o); + synchronized (m_locks[hash]) { + for (Node n = m_buckets[hash]; n != null; n = n.next) { + Object k = n.getKey(); + if ((k == o) || ((k != null) && k.equals(o))) { + StaticBucketMap.this.remove(k); + return true; + } + } + } + return false; + + } + + } + + + private class Values extends AbstractCollection { + + public int size() { + return StaticBucketMap.this.size(); + } + + public void clear() { + StaticBucketMap.this.clear(); + } + + public Iterator iterator() { + return new ValueIterator(); + } + + } + + + /** + * Prevents any operations from occuring on this map while the + * given {@link Runnable} executes. This method can be used, for + * instance, to execute a bulk operation atomicly: + * + *
+ * staticBucketMapInstance.atomic(new Runnable() { + * public void run() { + * staticBucketMapInstance.putAll(map); + * } + * }); + *+ * + * It can also be used if you need a reliable iterator: + * + *
+ * staticBucketMapInstance.atomic(new Runnable() { + * public void run() { + * Iterator iterator = staticBucketMapInstance.iterator(); + * while (iterator.hasNext()) { + * foo(iterator.next(); + * } + * } + * }); + *+ * + * Implementation note: This method requires a lot of time + * and a ton of stack space. Essentially a recursive algorithm is used + * to enter each bucket's monitor. If you have twenty thousand buckets + * in your map, then the recursive method will be invoked twenty thousand + * times. You have been warned. + * + * @param r the code to execute atomicly + */ + public void atomic(Runnable r) { + if (r == null) throw new NullPointerException(); + atomic(r, 0); + } + + private void atomic(Runnable r, int bucket) { + if (bucket >= m_buckets.length) { + r.run(); + return; + } + synchronized (m_locks[bucket]) { + atomic(r, bucket + 1); + } + } + + }