blob: ac7ab0e2ae8b8ec3a8c2e8c5ebf592d87ad24977 [file] [log] [blame]
/*
* 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();
}
}