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