| /* |
| * Copyright 2000-2014 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. |
| */ |
| |
| /* |
| * Created by IntelliJ IDEA. |
| * User: Alexey |
| * Date: 18.12.2006 |
| * Time: 20:18:31 |
| */ |
| package com.intellij.util.containers; |
| |
| import gnu.trove.TObjectHashingStrategy; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.lang.ref.ReferenceQueue; |
| import java.util.*; |
| import java.util.concurrent.ConcurrentMap; |
| |
| /** |
| * Base class for concurrent (soft/weak) key:K -> strong value:V map |
| * Null keys are allowed |
| * Null values are NOT allowed |
| */ |
| abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, TObjectHashingStrategy<K> { |
| protected final ReferenceQueue<K> myReferenceQueue = new ReferenceQueue<K>(); |
| private final ConcurrentMap<KeyReference<K, V>, V> myMap; // hashing strategy must be canonical, we compute corresponding hash codes using our own myHashingStrategy |
| @NotNull |
| private final TObjectHashingStrategy<K> myHashingStrategy; |
| |
| interface KeyReference<K, V> { |
| K get(); |
| |
| @NotNull |
| V getValue(); |
| |
| // MUST work even with gced references for the code in processQueue to work |
| boolean equals(Object o); |
| |
| int hashCode(); |
| } |
| |
| protected abstract KeyReference<K, V> createKeyReference(@NotNull K key, @NotNull V value, @NotNull TObjectHashingStrategy<K> hashingStrategy); |
| |
| private static final KeyReference NULL_KEY = new KeyReference() { |
| @Override |
| public Object get() { |
| return null; |
| } |
| |
| @NotNull |
| @Override |
| public Object getValue() { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| |
| // returns true if some keys were processed |
| boolean processQueue() { |
| KeyReference<K, V> wk; |
| boolean processed = false; |
| while ((wk = (KeyReference)myReferenceQueue.poll()) != null) { |
| V value = wk.getValue(); |
| myMap.remove(wk, value); |
| processed = true; |
| } |
| return processed; |
| } |
| |
| public ConcurrentRefHashMap(Map<? extends K, ? extends V> t) { |
| this(Math.max(2 * t.size(), 11), ConcurrentHashMap.DEFAULT_LOAD_FACTOR); |
| putAll(t); |
| } |
| |
| public ConcurrentRefHashMap() { |
| this(ConcurrentHashMap.DEFAULT_INITIAL_CAPACITY); |
| } |
| |
| public ConcurrentRefHashMap(int initialCapacity) { |
| this(initialCapacity, ConcurrentHashMap.DEFAULT_LOAD_FACTOR); |
| } |
| |
| private static final TObjectHashingStrategy THIS = new TObjectHashingStrategy() { |
| @Override |
| public int computeHashCode(Object object) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean equals(Object o1, Object o2) { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| public ConcurrentRefHashMap(int initialCapacity, float loadFactor) { |
| this(initialCapacity, loadFactor, 4, THIS); |
| } |
| |
| public ConcurrentRefHashMap(@NotNull final TObjectHashingStrategy<K> hashingStrategy) { |
| this(ConcurrentHashMap.DEFAULT_INITIAL_CAPACITY, ConcurrentHashMap.DEFAULT_LOAD_FACTOR, 2, hashingStrategy); |
| } |
| |
| public ConcurrentRefHashMap(int initialCapacity, |
| float loadFactor, |
| int concurrencyLevel, |
| @NotNull TObjectHashingStrategy<K> hashingStrategy) { |
| myHashingStrategy = hashingStrategy == THIS ? this : hashingStrategy; |
| myMap = ContainerUtil.<KeyReference<K, V>, V>newConcurrentMap(initialCapacity, loadFactor, concurrencyLevel, CANONICAL); |
| } |
| |
| @Override |
| public int size() { |
| return entrySet().size(); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return entrySet().isEmpty(); |
| } |
| |
| @Override |
| public boolean containsKey(Object key) { |
| // optimization: |
| if (key == null) { |
| return myMap.containsKey(NULL_KEY); |
| } |
| HardKey<K, V> hardKey = createHardKey(key); |
| boolean result = myMap.containsKey(hardKey); |
| releaseHardKey(hardKey); |
| return result; |
| } |
| |
| private static class HardKey<K, V> implements KeyReference<K, V> { |
| private K myKey; |
| private int myHash; |
| |
| private void setKey(K key, final int hash) { |
| myKey = key; |
| myHash = hash; |
| } |
| |
| @Override |
| public K get() { |
| return myKey; |
| } |
| |
| @NotNull |
| @Override |
| public V getValue() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean equals(Object o) { |
| return o.equals(this); // see com.intellij.util.containers.ConcurrentSoftHashMap.SoftKey or com.intellij.util.containers.ConcurrentWeakHashMap.WeakKey |
| } |
| |
| public int hashCode() { |
| return myHash; |
| } |
| } |
| private static final ThreadLocal<HardKey> HARD_KEY = new ThreadLocal<HardKey>() { |
| @Override |
| protected HardKey initialValue() { |
| return new HardKey(); |
| } |
| }; |
| |
| private HardKey<K, V> createHardKey(Object o) { |
| @SuppressWarnings("unchecked") K key = (K)o; |
| @SuppressWarnings("unchecked") HardKey<K,V> hardKey = HARD_KEY.get(); |
| hardKey.setKey(key, myHashingStrategy.computeHashCode(key)); |
| return hardKey; |
| } |
| |
| private static void releaseHardKey(HardKey<?,?> key) { |
| key.setKey(null, 0); |
| } |
| |
| @Override |
| public V get(Object key) { |
| //return myMap.get(WeakKey.create(key)); |
| // optimization: |
| if (key == null) { |
| return myMap.get(NULL_KEY); |
| } |
| HardKey<K, V> hardKey = createHardKey(key); |
| V result = myMap.get(hardKey); |
| releaseHardKey(hardKey); |
| return result; |
| } |
| |
| @Override |
| public V put(K key, @NotNull V value) { |
| processQueue(); |
| KeyReference<K, V> weakKey = key == null ? NULL_KEY : createKeyReference(key, value, myHashingStrategy); |
| return myMap.put(weakKey, value); |
| } |
| |
| @Override |
| public V remove(Object key) { |
| processQueue(); |
| |
| // optimization: |
| if (key == null) { |
| return myMap.remove(NULL_KEY); |
| } |
| HardKey hardKey = createHardKey(key); |
| V result = myMap.remove(hardKey); |
| releaseHardKey(hardKey); |
| return result; |
| } |
| |
| @Override |
| public void clear() { |
| processQueue(); |
| myMap.clear(); |
| } |
| |
| private static class RefEntry<K, V> implements Map.Entry<K, V> { |
| private final Map.Entry<?, V> ent; |
| private final K key; /* Strong reference to key, so that the GC |
| will leave it alone as long as this Entry |
| exists */ |
| |
| RefEntry(Map.Entry<?, V> ent, K key) { |
| this.ent = ent; |
| this.key = key; |
| } |
| |
| @Override |
| public K getKey() { |
| return key; |
| } |
| |
| @Override |
| public V getValue() { |
| return ent.getValue(); |
| } |
| |
| @Override |
| public V setValue(V value) { |
| return ent.setValue(value); |
| } |
| |
| private static boolean valEquals(Object o1, Object o2) { |
| return o1 == null ? o2 == null : o1.equals(o2); |
| } |
| |
| public boolean equals(Object o) { |
| if (!(o instanceof Map.Entry)) return false; |
| Map.Entry e = (Map.Entry)o; |
| return valEquals(key, e.getKey()) && valEquals(getValue(), e.getValue()); |
| } |
| |
| public int hashCode() { |
| Object v; |
| return (key == null ? 0 : key.hashCode()) ^ ((v = getValue()) == null ? 0 : v.hashCode()); |
| } |
| } |
| |
| /* Internal class for entry sets */ |
| private class EntrySet extends AbstractSet<Map.Entry<K, V>> { |
| Set<Map.Entry<KeyReference<K, V>, V>> hashEntrySet = myMap.entrySet(); |
| |
| @NotNull |
| @Override |
| public Iterator<Map.Entry<K, V>> iterator() { |
| return new Iterator<Map.Entry<K, V>>() { |
| Iterator<Map.Entry<KeyReference<K, V>, V>> hashIterator = hashEntrySet.iterator(); |
| RefEntry<K, V> next = null; |
| |
| @Override |
| public boolean hasNext() { |
| while (hashIterator.hasNext()) { |
| Map.Entry<KeyReference<K, V>, V> ent = hashIterator.next(); |
| KeyReference<K, V> wk = ent.getKey(); |
| K k = null; |
| if (wk != null && (k = wk.get()) == null) { |
| /* Weak key has been cleared by GC */ |
| continue; |
| } |
| next = new RefEntry<K, V>(ent, k); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public Map.Entry<K, V> next() { |
| if (next == null && !hasNext()) { |
| throw new NoSuchElementException(); |
| } |
| RefEntry<K, V> e = next; |
| next = null; |
| return e; |
| } |
| |
| @Override |
| public void remove() { |
| hashIterator.remove(); |
| } |
| }; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return !iterator().hasNext(); |
| } |
| |
| @Override |
| public int size() { |
| int j = 0; |
| for (Iterator i = iterator(); i.hasNext(); i.next()) j++; |
| return j; |
| } |
| |
| @Override |
| public boolean remove(Object o) { |
| processQueue(); |
| if (!(o instanceof Map.Entry)) return false; |
| Map.Entry<K,V> e = (Map.Entry)o; |
| V ev = e.getValue(); |
| |
| HardKey key = createHardKey(e.getKey()); |
| |
| V hv = myMap.get(key); |
| boolean toRemove = hv == null ? ev == null && myMap.containsKey(key) : hv.equals(ev); |
| if (toRemove) { |
| myMap.remove(key); |
| } |
| |
| releaseHardKey(key); |
| return toRemove; |
| } |
| |
| public int hashCode() { |
| int h = 0; |
| for (Object aHashEntrySet : hashEntrySet) { |
| Map.Entry ent = (Map.Entry)aHashEntrySet; |
| KeyReference wk = (KeyReference)ent.getKey(); |
| if (wk == null) continue; |
| Object v; |
| h += wk.hashCode() ^ ((v = ent.getValue()) == null ? 0 : v.hashCode()); |
| } |
| return h; |
| } |
| } |
| |
| private Set<Map.Entry<K, V>> entrySet = null; |
| |
| @NotNull |
| @Override |
| public Set<Map.Entry<K, V>> entrySet() { |
| if (entrySet == null) entrySet = new EntrySet(); |
| return entrySet; |
| } |
| |
| @Override |
| public V putIfAbsent(@NotNull final K key, @NotNull V value) { |
| processQueue(); |
| return myMap.putIfAbsent(createKeyReference(key, value, myHashingStrategy), value); |
| } |
| |
| @Override |
| public boolean remove(@NotNull final Object key, @NotNull Object value) { |
| processQueue(); |
| return myMap.remove(createKeyReference((K)key, (V)value, myHashingStrategy), value); |
| } |
| |
| @Override |
| public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) { |
| processQueue(); |
| return myMap.replace(createKeyReference(key, oldValue, myHashingStrategy), oldValue, newValue); |
| } |
| |
| @Override |
| public V replace(@NotNull final K key, @NotNull final V value) { |
| processQueue(); |
| return myMap.replace(createKeyReference(key, value, myHashingStrategy), value); |
| } |
| |
| // MAKE SURE IT CONSISTENT WITH com.intellij.util.containers.ConcurrentHashMap |
| @Override |
| public int computeHashCode(final K object) { |
| int h = object.hashCode(); |
| h += ~(h << 9); |
| h ^= (h >>> 14); |
| h += (h << 4); |
| h ^= (h >>> 10); |
| return h; |
| } |
| |
| @Override |
| public boolean equals(final K o1, final K o2) { |
| return o1.equals(o2); |
| } |
| |
| @TestOnly |
| int underlyingMapSize() { |
| return myMap.size(); |
| } |
| } |