001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.map;
018
019import java.util.AbstractCollection;
020import java.util.AbstractSet;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.NoSuchElementException;
026import java.util.Set;
027
028import org.apache.commons.collections4.KeyValue;
029
030/**
031 * A StaticBucketMap is an efficient, thread-safe implementation of
032 * <code>java.util.Map</code> that performs well in in a highly
033 * thread-contentious environment.  The map supports very efficient
034 * {@link #get(Object) get}, {@link #put(Object,Object) put},
035 * {@link #remove(Object) remove} and {@link #containsKey(Object) containsKey}
036 * operations, assuming (approximate) uniform hashing and
037 * that the number of entries does not exceed the number of buckets.  If the
038 * number of entries exceeds the number of buckets or if the hash codes of the
039 * objects are not uniformly distributed, these operations have a worst case
040 * scenario that is proportional to the number of elements in the map
041 * (<i>O(n)</i>).<p>
042 *
043 * Each bucket in the hash table has its own monitor, so two threads can
044 * safely operate on the map at the same time, often without incurring any
045 * monitor contention.  This means that you don't have to wrap instances
046 * of this class with {@link java.util.Collections#synchronizedMap(Map)};
047 * instances are already thread-safe.  Unfortunately, however, this means
048 * that this map implementation behaves in ways you may find disconcerting.
049 * Bulk operations, such as {@link #putAll(Map) putAll} or the
050 * {@link Collection#retainAll(Collection) retainAll} operation in collection
051 * views, are <i>not</i> atomic.  If two threads are simultaneously
052 * executing
053 *
054 * <pre>
055 *   staticBucketMapInstance.putAll(map);
056 * </pre>
057 *
058 * and
059 *
060 * <pre>
061 *   staticBucketMapInstance.entrySet().removeAll(map.entrySet());
062 * </pre>
063 *
064 * then the results are generally random.  Those two statement could cancel
065 * each other out, leaving <code>staticBucketMapInstance</code> essentially
066 * unchanged, or they could leave some random subset of <code>map</code> in
067 * <code>staticBucketMapInstance</code>.<p>
068 *
069 * Also, much like an encyclopedia, the results of {@link #size()} and
070 * {@link #isEmpty()} are out-of-date as soon as they are produced.<p>
071 *
072 * The iterators returned by the collection views of this class are <i>not</i>
073 * fail-fast.  They will <i>never</i> raise a
074 * {@link java.util.ConcurrentModificationException}.  Keys and values
075 * added to the map after the iterator is created do not necessarily appear
076 * during iteration.  Similarly, the iterator does not necessarily fail to
077 * return keys and values that were removed after the iterator was created.<p>
078 *
079 * Finally, unlike {@link java.util.HashMap}-style implementations, this
080 * class <i>never</i> rehashes the map.  The number of buckets is fixed
081 * at construction time and never altered.  Performance may degrade if
082 * you do not allocate enough buckets upfront.<p>
083 *
084 * The {@link #atomic(Runnable)} method is provided to allow atomic iterations
085 * and bulk operations; however, overuse of {@link #atomic(Runnable) atomic}
086 * will basically result in a map that's slower than an ordinary synchronized
087 * {@link java.util.HashMap}.
088 *
089 * Use this class if you do not require reliable bulk operations and
090 * iterations, or if you can make your own guarantees about how bulk
091 * operations will affect the map.<p>
092 *
093 * @since 3.0 (previously in main package v2.1)
094 * @version $Id: StaticBucketMap.html 972421 2015-11-14 20:00:04Z tn $
095 */
096public final class StaticBucketMap<K, V> extends AbstractIterableMap<K, V> {
097
098    /** The default number of buckets to use */
099    private static final int DEFAULT_BUCKETS = 255;
100    /** The array of buckets, where the actual data is held */
101    private final Node<K, V>[] buckets;
102    /** The matching array of locks */
103    private final Lock[] locks;
104
105    /**
106     * Initializes the map with the default number of buckets (255).
107     */
108    public StaticBucketMap() {
109        this(DEFAULT_BUCKETS);
110    }
111
112    /**
113     * Initializes the map with a specified number of buckets.  The number
114     * of buckets is never below 17, and is always an odd number (StaticBucketMap
115     * ensures this). The number of buckets is inversely proportional to the
116     * chances for thread contention.  The fewer buckets, the more chances for
117     * thread contention.  The more buckets the fewer chances for thread
118     * contention.
119     *
120     * @param numBuckets  the number of buckets for this map
121     */
122    @SuppressWarnings("unchecked")
123    public StaticBucketMap(final int numBuckets) {
124        int size = Math.max(17, numBuckets);
125
126        // Ensure that bucketSize is never a power of 2 (to ensure maximal distribution)
127        if (size % 2 == 0) {
128            size--;
129        }
130
131        buckets = new Node[size];
132        locks = new Lock[size];
133
134        for (int i = 0; i < size; i++) {
135            locks[i] = new Lock();
136        }
137    }
138
139    //-----------------------------------------------------------------------
140    /**
141     * Determine the exact hash entry for the key.  The hash algorithm
142     * is rather simplistic, but it does the job:
143     *
144     * <pre>
145     *   He = |Hk mod n|
146     * </pre>
147     *
148     * <p>
149     *   He is the entry's hashCode, Hk is the key's hashCode, and n is
150     *   the number of buckets.
151     * </p>
152     */
153    private int getHash(final Object key) {
154        if (key == null) {
155            return 0;
156        }
157        int hash = key.hashCode();
158        hash += ~(hash << 15);
159        hash ^= (hash >>> 10);
160        hash += (hash << 3);
161        hash ^= (hash >>> 6);
162        hash += ~(hash << 11);
163        hash ^= (hash >>> 16);
164        hash %= buckets.length;
165        return (hash < 0) ? hash * -1 : hash;
166    }
167
168    /**
169     * Gets the current size of the map.
170     * The value is computed fresh each time the method is called.
171     *
172     * @return the current size
173     */
174    public int size() {
175        int cnt = 0;
176
177        for (int i = 0; i < buckets.length; i++) {
178            synchronized(locks[i]) {
179                cnt += locks[i].size;
180            }
181        }
182        return cnt;
183    }
184
185    /**
186     * Checks if the size is currently zero.
187     *
188     * @return true if empty
189     */
190    public boolean isEmpty() {
191        return (size() == 0);
192    }
193
194    /**
195     * Gets the value associated with the key.
196     *
197     * @param key  the key to retrieve
198     * @return the associated value
199     */
200    public V get(final Object key) {
201        final int hash = getHash(key);
202
203        synchronized (locks[hash]) {
204            Node<K, V> n = buckets[hash];
205
206            while (n != null) {
207                if (n.key == key || (n.key != null && n.key.equals(key))) {
208                    return n.value;
209                }
210
211                n = n.next;
212            }
213        }
214        return null;
215    }
216
217    /**
218     * Checks if the map contains the specified key.
219     *
220     * @param key  the key to check
221     * @return true if found
222     */
223    public boolean containsKey(final Object key) {
224        final int hash = getHash(key);
225
226        synchronized (locks[hash]) {
227            Node<K, V> n = buckets[hash];
228
229            while (n != null) {
230                if (n.key == key || (n.key != null && n.key.equals(key))) {
231                    return true;
232                }
233
234                n = n.next;
235            }
236        }
237        return false;
238    }
239
240    /**
241     * Checks if the map contains the specified value.
242     *
243     * @param value  the value to check
244     * @return true if found
245     */
246    public boolean containsValue(final Object value) {
247        for (int i = 0; i < buckets.length; i++) {
248            synchronized (locks[i]) {
249                Node<K, V> n = buckets[i];
250
251                while (n != null) {
252                    if (n.value == value || (n.value != null && n.value.equals(value))) {
253                        return true;
254                    }
255
256                    n = n.next;
257                }
258            }
259        }
260        return false;
261    }
262
263    //-----------------------------------------------------------------------
264    /**
265     * Puts a new key value mapping into the map.
266     *
267     * @param key  the key to use
268     * @param value  the value to use
269     * @return the previous mapping for the key
270     */
271    public V put(final K key, final V value) {
272        final int hash = getHash(key);
273
274        synchronized (locks[hash]) {
275            Node<K, V> n = buckets[hash];
276
277            if (n == null) {
278                n = new Node<K, V>();
279                n.key = key;
280                n.value = value;
281                buckets[hash] = n;
282                locks[hash].size++;
283                return null;
284            }
285
286            // Set n to the last node in the linked list.  Check each key along the way
287            //  If the key is found, then change the value of that node and return
288            //  the old value.
289            for (Node<K, V> next = n; next != null; next = next.next) {
290                n = next;
291
292                if (n.key == key || (n.key != null && n.key.equals(key))) {
293                    final V returnVal = n.value;
294                    n.value = value;
295                    return returnVal;
296                }
297            }
298
299            // The key was not found in the current list of nodes, add it to the end
300            //  in a new node.
301            final Node<K, V> newNode = new Node<K, V>();
302            newNode.key = key;
303            newNode.value = value;
304            n.next = newNode;
305            locks[hash].size++;
306        }
307        return null;
308    }
309
310    /**
311     * Removes the specified key from the map.
312     *
313     * @param key  the key to remove
314     * @return the previous value at this key
315     */
316    public V remove(final Object key) {
317        final int hash = getHash(key);
318
319        synchronized (locks[hash]) {
320            Node<K, V> n = buckets[hash];
321            Node<K, V> prev = null;
322
323            while (n != null) {
324                if (n.key == key || (n.key != null && n.key.equals(key))) {
325                    // Remove this node from the linked list of nodes.
326                    if (null == prev) {
327                        // This node was the head, set the next node to be the new head.
328                        buckets[hash] = n.next;
329                    } else {
330                        // Set the next node of the previous node to be the node after this one.
331                        prev.next = n.next;
332                    }
333                    locks[hash].size--;
334                    return n.value;
335                }
336
337                prev = n;
338                n = n.next;
339            }
340        }
341        return null;
342    }
343
344    //-----------------------------------------------------------------------
345    /**
346     * Gets the key set.
347     *
348     * @return the key set
349     */
350    public Set<K> keySet() {
351        return new KeySet();
352    }
353
354    /**
355     * Gets the values.
356     *
357     * @return the values
358     */
359    public Collection<V> values() {
360        return new Values();
361    }
362
363    /**
364     * Gets the entry set.
365     *
366     * @return the entry set
367     */
368    public Set<Map.Entry<K, V>> entrySet() {
369        return new EntrySet();
370    }
371
372    //-----------------------------------------------------------------------
373    /**
374     * Puts all the entries from the specified map into this map.
375     * This operation is <b>not atomic</b> and may have undesired effects.
376     *
377     * @param map  the map of entries to add
378     */
379    public void putAll(final Map<? extends K, ? extends V> map) {
380        for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
381            put(entry.getKey(), entry.getValue());
382        }
383    }
384
385    /**
386     * Clears the map of all entries.
387     */
388    public void clear() {
389        for (int i = 0; i < buckets.length; i++) {
390            final Lock lock = locks[i];
391            synchronized (lock) {
392                buckets[i] = null;
393                lock.size = 0;
394            }
395        }
396    }
397
398    /**
399     * Compares this map to another, as per the Map specification.
400     *
401     * @param obj  the object to compare to
402     * @return true if equal
403     */
404    @Override
405    public boolean equals(final Object obj) {
406        if (obj == this) {
407            return true;
408        }
409        if (obj instanceof Map<?, ?> == false) {
410            return false;
411        }
412        final Map<?, ?> other = (Map<?, ?>) obj;
413        return entrySet().equals(other.entrySet());
414    }
415
416    /**
417     * Gets the hash code, as per the Map specification.
418     *
419     * @return the hash code
420     */
421    @Override
422    public int hashCode() {
423        int hashCode = 0;
424
425        for (int i = 0; i < buckets.length; i++) {
426            synchronized (locks[i]) {
427                Node<K, V> n = buckets[i];
428
429                while (n != null) {
430                    hashCode += n.hashCode();
431                    n = n.next;
432                }
433            }
434        }
435        return hashCode;
436    }
437
438    //-----------------------------------------------------------------------
439    /**
440     * The Map.Entry for the StaticBucketMap.
441     */
442    private static final class Node<K, V> implements Map.Entry<K, V>, KeyValue<K, V> {
443        protected K key;
444        protected V value;
445        protected Node<K, V> next;
446
447        public K getKey() {
448            return key;
449        }
450
451        public V getValue() {
452            return value;
453        }
454
455        @Override
456        public int hashCode() {
457            return ((key == null ? 0 : key.hashCode()) ^
458                    (value == null ? 0 : value.hashCode()));
459        }
460
461        @Override
462        public boolean equals(final Object obj) {
463            if (obj == this) {
464                return true;
465            }
466            if (obj instanceof Map.Entry<?, ?> == false) {
467                return false;
468            }
469
470            final Map.Entry<?, ?> e2 = (Map.Entry<?, ?>) obj;
471            return (
472                (key == null ? e2.getKey() == null : key.equals(e2.getKey())) &&
473                (value == null ? e2.getValue() == null : value.equals(e2.getValue())));
474        }
475
476        public V setValue(final V obj) {
477            final V retVal = value;
478            value = obj;
479            return retVal;
480        }
481    }
482
483    /**
484     * The lock object, which also includes a count of the nodes in this lock.
485     */
486    private final static class Lock {
487        public int size;
488    }
489
490    //-----------------------------------------------------------------------
491    private class BaseIterator {
492        private final ArrayList<Map.Entry<K, V>> current = new ArrayList<Map.Entry<K,V>>();
493        private int bucket;
494        private Map.Entry<K, V> last;
495
496        public boolean hasNext() {
497            if (current.size() > 0) {
498                return true;
499            }
500            while (bucket < buckets.length) {
501                synchronized (locks[bucket]) {
502                    Node<K, V> n = buckets[bucket];
503                    while (n != null) {
504                        current.add(n);
505                        n = n.next;
506                    }
507                    bucket++;
508                    if (current.size() > 0) {
509                        return true;
510                    }
511                }
512            }
513            return false;
514        }
515
516        protected Map.Entry<K, V> nextEntry() {
517            if (!hasNext()) {
518                throw new NoSuchElementException();
519            }
520            last = current.remove(current.size() - 1);
521            return last;
522        }
523
524        public void remove() {
525            if (last == null) {
526                throw new IllegalStateException();
527            }
528            StaticBucketMap.this.remove(last.getKey());
529            last = null;
530        }
531    }
532
533    private class EntryIterator extends BaseIterator implements Iterator<Map.Entry<K, V>> {
534
535        public Map.Entry<K, V> next() {
536            return nextEntry();
537        }
538
539    }
540
541    private class ValueIterator extends BaseIterator implements Iterator<V> {
542
543        public V next() {
544            return nextEntry().getValue();
545        }
546
547    }
548
549    private class KeyIterator extends BaseIterator implements Iterator<K> {
550
551        public K next() {
552            return nextEntry().getKey();
553        }
554
555    }
556
557    private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
558
559        @Override
560        public int size() {
561            return StaticBucketMap.this.size();
562        }
563
564        @Override
565        public void clear() {
566            StaticBucketMap.this.clear();
567        }
568
569        @Override
570        public Iterator<Map.Entry<K, V>> iterator() {
571            return new EntryIterator();
572        }
573
574        @Override
575        public boolean contains(final Object obj) {
576            final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
577            final int hash = getHash(entry.getKey());
578            synchronized (locks[hash]) {
579                for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
580                    if (n.equals(entry)) {
581                        return true;
582                    }
583                }
584            }
585            return false;
586        }
587
588        @Override
589        public boolean remove(final Object obj) {
590            if (obj instanceof Map.Entry<?, ?> == false) {
591                return false;
592            }
593            final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
594            final int hash = getHash(entry.getKey());
595            synchronized (locks[hash]) {
596                for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
597                    if (n.equals(entry)) {
598                        StaticBucketMap.this.remove(n.getKey());
599                        return true;
600                    }
601                }
602            }
603            return false;
604        }
605
606    }
607
608    private class KeySet extends AbstractSet<K> {
609
610        @Override
611        public int size() {
612            return StaticBucketMap.this.size();
613        }
614
615        @Override
616        public void clear() {
617            StaticBucketMap.this.clear();
618        }
619
620        @Override
621        public Iterator<K> iterator() {
622            return new KeyIterator();
623        }
624
625        @Override
626        public boolean contains(final Object obj) {
627            return StaticBucketMap.this.containsKey(obj);
628        }
629
630        @Override
631        public boolean remove(final Object obj) {
632            final int hash = getHash(obj);
633            synchronized (locks[hash]) {
634                for (Node<K, V> n = buckets[hash]; n != null; n = n.next) {
635                    final Object k = n.getKey();
636                    if ((k == obj) || ((k != null) && k.equals(obj))) {
637                        StaticBucketMap.this.remove(k);
638                        return true;
639                    }
640                }
641            }
642            return false;
643        }
644
645    }
646
647
648    private class Values extends AbstractCollection<V> {
649
650        @Override
651        public int size() {
652            return StaticBucketMap.this.size();
653        }
654
655        @Override
656        public void clear() {
657            StaticBucketMap.this.clear();
658        }
659
660        @Override
661        public Iterator<V> iterator() {
662            return new ValueIterator();
663        }
664
665    }
666
667    /**
668     *  Prevents any operations from occurring on this map while the
669     *  given {@link Runnable} executes.  This method can be used, for
670     *  instance, to execute a bulk operation atomically:
671     *
672     *  <pre>
673     *    staticBucketMapInstance.atomic(new Runnable() {
674     *        public void run() {
675     *            staticBucketMapInstance.putAll(map);
676     *        }
677     *    });
678     *  </pre>
679     *
680     *  It can also be used if you need a reliable iterator:
681     *
682     *  <pre>
683     *    staticBucketMapInstance.atomic(new Runnable() {
684     *        public void run() {
685     *            Iterator iterator = staticBucketMapInstance.iterator();
686     *            while (iterator.hasNext()) {
687     *                foo(iterator.next();
688     *            }
689     *        }
690     *    });
691     *  </pre>
692     *
693     *  <b>Implementation note:</b> This method requires a lot of time
694     *  and a ton of stack space.  Essentially a recursive algorithm is used
695     *  to enter each bucket's monitor.  If you have twenty thousand buckets
696     *  in your map, then the recursive method will be invoked twenty thousand
697     *  times.  You have been warned.
698     *
699     *  @param r  the code to execute atomically
700     */
701    public void atomic(final Runnable r) {
702        if (r == null) {
703            throw new NullPointerException();
704        }
705        atomic(r, 0);
706    }
707
708    private void atomic(final Runnable r, final int bucket) {
709        if (bucket >= buckets.length) {
710            r.run();
711            return;
712        }
713        synchronized (locks[bucket]) {
714            atomic(r, bucket + 1);
715        }
716    }
717
718}