/* * Copyright 2000-2010 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.util.containers; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentMap; /** * similar to java.util.ConcurrentHashMap except: * conserved as much memory as possible by * -- using only one Segment * -- eliminating unnecessary fields * -- using one of 256 ReentrantLock for Segment statically preallocated in {@link StripedReentrantLocks} * added hashing strategy argument * made not Serializable */ public class StripedLockConcurrentHashMap<K, V> extends _CHMSegment<K, V> implements ConcurrentMap<K, V> { /* ---------------- Constants -------------- */ /** * The default initial number of table slots for this table. * Used when not otherwise specified in constructor. */ static int DEFAULT_INITIAL_CAPACITY = 16; /** * The maximum capacity, used if a higher value is implicitly * specified by either of the constructors with arguments. MUST * be a power of two <= 1<<30 to ensure that entries are indexible * using ints. */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The default load factor for this table. Used when not * otherwise specified in constructor. */ public static final float DEFAULT_LOAD_FACTOR = 0.75f; /* ---------------- Public operations -------------- */ public static <K,V> StripedLockConcurrentHashMap<K,V> createWithStrategy(@NotNull final TObjectHashingStrategy<K> hashingStrategy, int initialCapacity) { return new StripedLockConcurrentHashMap<K, V>(initialCapacity){ @Override protected TObjectHashingStrategy<K> getHashingStrategy() { return hashingStrategy; } }; } public StripedLockConcurrentHashMap(int initialCapacity) { super(getInitCap(initialCapacity, DEFAULT_LOAD_FACTOR)); } private static int getInitCap(int initialCapacity, float loadFactor) { if (loadFactor <= 0 || initialCapacity < 0) { throw new IllegalArgumentException(); } if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } int cap = 1; while (cap < initialCapacity) { cap <<= 1; } return cap; } /** * Creates a new, empty map with a default initial capacity, * load factor, and concurrencyLevel. */ public StripedLockConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY); } /** * Creates a new map with the same mappings as the given map. The * map is created with a capacity of twice the number of mappings in * the given map or 11 (whichever is greater), and a default load factor * and concurrencyLevel. * * @param t the map */ public StripedLockConcurrentHashMap(@NotNull Map<? extends K, ? extends V> t) { this(Math.max((int)(t.size() / DEFAULT_LOAD_FACTOR) + 1, 11)); putAll(t); } // inherit Map javadoc public boolean isEmpty() { return count == 0; } // inherit Map javadoc public int size() { return count; } /** * Returns the value to which the specified key is mapped in this table. * * @param key a key in the table. * @return the value to which the key is mapped in this table; * <tt>null</tt> if the key is not mapped to any value in * this table. * @throws NullPointerException if the key is * <tt>null</tt>. */ public V get(@NotNull Object key) { K kKey = (K)key; int hash = getHashingStrategy().computeHashCode(kKey); // throws NullPointerException if key null return get(kKey, hash); } /** * Tests if the specified object is a key in this table. * * @param key possible key. * @return <tt>true</tt> if and only if the specified object * is a key in this table, as determined by the * <tt>equals</tt> method; <tt>false</tt> otherwise. * @throws NullPointerException if the key is * <tt>null</tt>. */ public boolean containsKey(@NotNull Object key) { K kKey = (K)key; int hash = getHashingStrategy().computeHashCode(kKey); // throws NullPointerException if key null return containsKey(kKey, hash); } /** * Legacy method testing if some key maps into the specified value * in this table. This method is identical in functionality to * {@link #containsValue}, and exists solely to ensure * full compatibility with class {@link java.util.Hashtable}, * which supported this method prior to introduction of the * Java Collections framework. * * @param value a value to search for. * @return <tt>true</tt> if and only if some key maps to the * <tt>value</tt> argument in this table as * determined by the <tt>equals</tt> method; * <tt>false</tt> otherwise. * @throws NullPointerException if the value is <tt>null</tt>. */ public boolean contains(@NotNull Object value) { return containsValue(value); } /** * Maps the specified <tt>key</tt> to the specified * <tt>value</tt> in this table. Neither the key nor the * value can be <tt>null</tt>. * <p/> * <p> The value can be retrieved by calling the <tt>get</tt> method * with a key that is equal to the original key. * * @param key the table key. * @param value the value. * @return the previous value of the specified key in this table, * or <tt>null</tt> if it did not have one. * @throws NullPointerException if the key or value is * <tt>null</tt>. */ public V put(@NotNull K key, @NotNull V value) { int hash = getHashingStrategy().computeHashCode(key); return put(key, hash, value, false); } /** * If the specified key is not already associated * with a value, associate it with the given value. * This is equivalent to * <pre> * if (!map.containsKey(key)) * return map.put(key, value); * else * return map.get(key); * </pre> * Except that the action is performed atomically. * * @param key key with which the specified value is to be associated. * @param value value to be associated with the specified key. * @return previous value associated with specified key, or <tt>null</tt> * if there was no mapping for key. * @throws NullPointerException if the specified key or value is * <tt>null</tt>. */ public V putIfAbsent(@NotNull K key, @NotNull V value) { int hash = getHashingStrategy().computeHashCode(key); return put(key, hash, value, true); } /** * Copies all of the mappings from the specified map to this one. * <p/> * These mappings replace any mappings that this map had for any of the * keys currently in the specified Map. * * @param t Mappings to be stored in this map. */ public void putAll(@NotNull Map<? extends K, ? extends V> t) { for (Entry<? extends K, ? extends V> e : t.entrySet()) { V value = e.getValue(); if (value != null) { // null is possible if the entry has just been removed put(e.getKey(), value); } } } /** * Removes the key (and its corresponding value) from this * table. This method does nothing if the key is not in the table. * * @param key the key that needs to be removed. * @return the value to which the key had been mapped in this table, * or <tt>null</tt> if the key did not have a mapping. * @throws NullPointerException if the key is * <tt>null</tt>. */ public V remove(@NotNull Object key) { K kKey = (K)key; int hash = getHashingStrategy().computeHashCode(kKey); return remove(kKey, hash, null); } /** * Remove entry for key only if currently mapped to given value. * Acts as * <pre> * if (map.get(key).equals(value)) { * map.remove(key); * return true; * } else return false; * </pre> * except that the action is performed atomically. * * @param key key with which the specified value is associated. * @param value value associated with the specified key. * @return true if the value was removed * @throws NullPointerException if the specified key is * <tt>null</tt>. */ public boolean remove(@NotNull Object key, @NotNull Object value) { K kKey = (K)key; int hash = getHashingStrategy().computeHashCode(kKey); return remove(kKey, hash, value) != null; } /** * Replace entry for key only if currently mapped to given value. * Acts as * <pre> * if (map.get(key).equals(oldValue)) { * map.put(key, newValue); * return true; * } else return false; * </pre> * except that the action is performed atomically. * * @param key key with which the specified value is associated. * @param oldValue value expected to be associated with the specified key. * @param newValue value to be associated with the specified key. * @return true if the value was replaced * @throws NullPointerException if the specified key or values are * <tt>null</tt>. */ public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) { int hash = getHashingStrategy().computeHashCode(key); return replace(key, hash, oldValue, newValue); } /** * Replace entry for key only if currently mapped to some value. * Acts as * <pre> * if ((map.containsKey(key)) { * return map.put(key, value); * } else return null; * </pre> * except that the action is performed atomically. * * @param key key with which the specified value is associated. * @param value value to be associated with the specified key. * @return previous value associated with specified key, or <tt>null</tt> * if there was no mapping for key. * @throws NullPointerException if the specified key or value is * <tt>null</tt>. */ public V replace(@NotNull K key, @NotNull V value) { int hash = getHashingStrategy().computeHashCode(key); return replace(key, hash, value); } /** * Returns a set view of the keys contained in this map. The set is * backed by the map, so changes to the map are reflected in the set, and * vice-versa. The set supports element removal, which removes the * corresponding mapping from this map, via the <tt>Iterator.remove</tt>, * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and * <tt>clear</tt> operations. It does not support the <tt>add</tt> or * <tt>addAll</tt> operations. * The view's returned <tt>iterator</tt> is a "weakly consistent" iterator that * will never throw {@link java.util.ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. * * @return a set view of the keys contained in this map. */ public Set<K> keySet() { return new KeySet(); //conserve memory by not caching keyset } /** * Returns a collection view of the values contained in this map. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. The collection supports element * removal, which removes the corresponding mapping from this map, via the * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations. * It does not support the <tt>add</tt> or <tt>addAll</tt> operations. * The view's returned <tt>iterator</tt> is a "weakly consistent" iterator that * will never throw {@link java.util.ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. * * @return a collection view of the values contained in this map. */ public Collection<V> values() { return new Values(); //conserve memory by not caching } /** * Returns a collection view of the mappings contained in this map. Each * element in the returned collection is a <tt>Map.Entry</tt>. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. The collection supports element * removal, which removes the corresponding mapping from the map, via the * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations. * It does not support the <tt>add</tt> or <tt>addAll</tt> operations. * The view's returned <tt>iterator</tt> is a "weakly consistent" iterator that * will never throw {@link java.util.ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. * * @return a collection view of the mappings contained in this map. */ public Set<Entry<K, V>> entrySet() { return new EntrySet(); //conserve memory by not caching } /** * Returns an enumeration of the keys in this table. * * @return an enumeration of the keys in this table. * @see #keySet */ public Enumeration<K> keys() { return new KeyIterator(); } /** * Returns an enumeration of the values in this table. * * @return an enumeration of the values in this table. * @see #values */ public Enumeration<V> elements() { return new ValueIterator(); } /* ---------------- Iterator Support -------------- */ abstract class HashIterator { int nextSegmentIndex; int nextTableIndex; HashEntry[] currentTable; HashEntry<K, V> nextEntry; HashEntry<K, V> lastReturned; HashIterator() { nextSegmentIndex = 0; nextTableIndex = -1; advance(); } public boolean hasMoreElements() { return hasNext(); } final void advance() { if (nextEntry != null && (nextEntry = nextEntry.next) != null) { return; } while (nextTableIndex >= 0) { if ((nextEntry = (HashEntry<K, V>)currentTable[nextTableIndex--]) != null) { return; } } while (nextSegmentIndex >= 0) { _CHMSegment seg = StripedLockConcurrentHashMap.this; nextSegmentIndex--; if (seg.count != 0) { currentTable = seg.table; for (int j = currentTable.length - 1; j >= 0; --j) { if ((nextEntry = (HashEntry<K, V>)currentTable[j]) != null) { nextTableIndex = j - 1; return; } } } } } public boolean hasNext() { return nextEntry != null; } HashEntry<K, V> nextEntry() { if (nextEntry == null) { throw new NoSuchElementException(); } lastReturned = nextEntry; advance(); return lastReturned; } public void remove() { if (lastReturned == null) { throw new IllegalStateException(); } StripedLockConcurrentHashMap.this.remove(lastReturned.key); lastReturned = null; } } final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> { @NotNull public K next() { return nextEntry().key; } @NotNull public K nextElement() { return nextEntry().key; } } final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> { @NotNull public V next() { return nextEntry().value; } @NotNull public V nextElement() { return nextEntry().value; } } /** * Entry iterator. Exported Entry objects must write-through * changes in setValue, even if the nodes have been cloned. So we * cannot return internal HashEntry objects. Instead, the iterator * itself acts as a forwarding pseudo-entry. */ final class EntryIterator extends HashIterator implements Entry<K, V>, Iterator<Entry<K, V>> { @NotNull public Entry<K, V> next() { nextEntry(); return this; } @NotNull public K getKey() { if (lastReturned == null) { throw new IllegalStateException("Entry was removed"); } return lastReturned.key; } @Nullable("null means the entry has just been removed") public V getValue() { if (lastReturned == null) { throw new IllegalStateException("Entry was removed"); } return get(lastReturned.key); } public V setValue(@NotNull V value) { if (lastReturned == null) { throw new IllegalStateException("Entry was removed"); } return put(lastReturned.key, value); } public boolean equals(Object o) { // If not acting as entry, just use default. if (lastReturned == null) { return super.equals(o); } if (!(o instanceof Entry)) { return false; } Entry e = (Entry)o; K o1 = getKey(); K o2 = (K)e.getKey(); return getHashingStrategy().equals(o1, o2) && getValue().equals(e.getValue()); } public int hashCode() { // If not acting as entry, just use default. if (lastReturned == null) { return super.hashCode(); } Object k = getKey(); Object v = getValue(); return k.hashCode() ^ v.hashCode(); } public String toString() { // If not acting as entry, just use default. if (lastReturned == null) { return super.toString(); } return getKey() + "=" + getValue(); } } final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return new KeyIterator(); } public int size() { return StripedLockConcurrentHashMap.this.size(); } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return StripedLockConcurrentHashMap.this.remove(o) != null; } public void clear() { StripedLockConcurrentHashMap.this.clear(); } public Object[] toArray() { Collection<K> c = new ArrayList<K>(); for (K k : this) { c.add(k); } return c.toArray(); } public <T> T[] toArray(T[] a) { Collection<K> c = new ArrayList<K>(); for (K k : this) { c.add(k); } return c.toArray(a); } } final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return new ValueIterator(); } public int size() { return StripedLockConcurrentHashMap.this.size(); } public boolean contains(Object o) { return containsValue(o); } public void clear() { StripedLockConcurrentHashMap.this.clear(); } public Object[] toArray() { Collection<V> c = new ArrayList<V>(); for (V k : this) { c.add(k); } return c.toArray(); } public <T> T[] toArray(T[] a) { Collection<V> c = new ArrayList<V>(); for (V k : this) { c.add(k); } return c.toArray(a); } } final class EntrySet extends AbstractSet<Entry<K, V>> { public Iterator<Entry<K, V>> iterator() { return new EntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Entry)) { return false; } Entry<K, V> e = (Entry<K, V>)o; V v = get(e.getKey()); return v != null && v.equals(e.getValue()); } public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } Entry<K, V> e = (Entry<K, V>)o; return StripedLockConcurrentHashMap.this.remove(e.getKey(), e.getValue()); } public int size() { return StripedLockConcurrentHashMap.this.size(); } public void clear() { StripedLockConcurrentHashMap.this.clear(); } public Object[] toArray() { // Since we don't ordinarily have distinct Entry objects, we // must pack elements using exportable SimpleEntry Collection<Entry<K, V>> c = new ArrayList<Entry<K, V>>(size()); for (Entry<K, V> i : this) { c.add(new SimpleEntry(i)); } return c.toArray(); } public <T> T[] toArray(T[] a) { Collection<Entry<K, V>> c = new ArrayList<Entry<K, V>>(size()); for (Entry<K, V> i : this) { c.add(new SimpleEntry(i)); } return c.toArray(a); } } /** * This duplicates java.util.AbstractMap.SimpleEntry until this class * is made accessible. */ final class SimpleEntry implements Entry<K, V> { final K key; V value; public SimpleEntry(@NotNull Entry<K, V> e) { key = e.getKey(); value = e.getValue(); } @NotNull public K getKey() { return key; } @NotNull public V getValue() { return value; } public V setValue(@NotNull V value) { V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Entry)) { return false; } Entry e = (Entry)o; K o2 = (K)e.getKey(); return getHashingStrategy().equals(key, o2) && value.equals(e.getValue()); } public int hashCode() { return key.hashCode() ^ value.hashCode(); } public String toString() { return key + "=" + value; } } static class CanonicalHashingStrategy<K> implements TObjectHashingStrategy<K> { private static final CanonicalHashingStrategy INSTANCE = new CanonicalHashingStrategy(); @SuppressWarnings("unchecked") static <K> CanonicalHashingStrategy<K> getInstance() { return INSTANCE; } public int computeHashCode(final K object) { int h = object.hashCode(); // performance matters here //h += ~(h << 9); //h ^= h >>> 14; //h += h << 4; //h ^= h >>> 10; return h; } public boolean equals(@NotNull K o1, @NotNull K o2) { return o1.equals(o2); } } }