blob: 36d83d104e0c255796839f3554099b82fc49c070 [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 com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Getter;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import static com.intellij.reference.SoftReference.deref;
/**
* Concurrent map with weak keys and strong values.
* Null keys are NOT allowed
* Null values are NOT allowed
*/
public class ConcurrentWeakKeySoftValueHashMap<K, V> implements ConcurrentMap<K, V> {
private final ConcurrentMap<KeyReference<K,V>, ValueReference<K,V>> myMap;
private final ReferenceQueue<K> myKeyQueue = new ReferenceQueue<K>();
private final ReferenceQueue<V> myValueQueue = new ReferenceQueue<V>();
@NotNull private final TObjectHashingStrategy<K> myHashingStrategy;
public ConcurrentWeakKeySoftValueHashMap(int initialCapacity,
float loadFactor,
int concurrencyLevel,
@NotNull final TObjectHashingStrategy<K> hashingStrategy) {
myHashingStrategy = hashingStrategy;
myMap = new ConcurrentHashMap<KeyReference<K, V>, ValueReference<K, V>>(initialCapacity, loadFactor, concurrencyLevel, TObjectHashingStrategy.CANONICAL);
}
public interface KeyReference<K, V> extends Getter<K> {
K get();
@NotNull
ValueReference<K,V> getValueReference(); // no strong references
// MUST work even with gced references for the code in processQueue to work
boolean equals(Object o);
int hashCode();
}
public interface ValueReference<K, V> extends Getter<V> {
@NotNull
KeyReference<K,V> getKeyReference(); // no strong references
V get();
}
private static class WeakKey<K, V> extends WeakReference<K> implements KeyReference<K, V> {
private final int myHash; // Hash code of the key, stored here since the key may be tossed by the GC
private final TObjectHashingStrategy<K> myStrategy;
@NotNull private final ValueReference<K, V> myValueReference;
private WeakKey(@NotNull K k, @NotNull ValueReference<K, V> valueReference, @NotNull TObjectHashingStrategy<K> strategy, @NotNull ReferenceQueue<K> queue) {
super(k, queue);
myValueReference = valueReference;
myHash = strategy.computeHashCode(k);
myStrategy = strategy;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof KeyReference)) return false;
K t = get();
K other = ((KeyReference<K,V>)o).get();
if (t == null || other == null) return false;
if (t == other) return true;
return myHash == o.hashCode() && myStrategy.equals(t, other);
}
public int hashCode() {
return myHash;
}
@NotNull
@Override
public ValueReference<K, V> getValueReference() {
return myValueReference;
}
}
private static class SoftValue<K, V> extends SoftReference<V> implements ValueReference<K,V> {
@NotNull private volatile KeyReference<K, V> myKeyReference; // can't make it final because of circular dependency of KeyReference to ValueReference
private final int myHash; // Hashcode of key, stored here since the key may be tossed by the GC
private SoftValue(@NotNull V value, @NotNull ReferenceQueue<V> queue) {
super(value, queue);
myHash = value.hashCode();
}
// MUST work with gced references too for the code in processQueue to work
public final boolean equals(final Object o) {
if (this == o) return true;
if (o == null) return false;
ValueReference that = (ValueReference)o;
return myHash == that.hashCode() && Comparing.equal(get(), that.get());
}
public final int hashCode() {
return myHash;
}
@NotNull
@Override
public KeyReference<K, V> getKeyReference() {
return myKeyReference;
}
}
@NotNull
protected KeyReference<K,V> createKeyReference(@NotNull K k, @NotNull final V v) {
final ValueReference<K, V> valueReference = createValueReference(v, myValueQueue);
WeakKey<K, V> keyReference = new WeakKey<K, V>(k, valueReference, myHashingStrategy, myKeyQueue);
if (valueReference instanceof SoftValue) {
((SoftValue)valueReference).myKeyReference = keyReference;
}
return keyReference;
}
@NotNull
protected ValueReference<K, V> createValueReference(@NotNull V value, @NotNull ReferenceQueue<V> queue) {
return new SoftValue<K, V>(value, queue);
}
@Override
public int size() {
return myMap.size();
}
@Override
public boolean isEmpty() {
return myMap.isEmpty();
}
@Override
public void clear() {
processQueues();
myMap.clear();
}
/////////////////////////////
private static class HardKey<K,V> implements KeyReference<K,V> {
private K myKey;
private int myHash;
private void set(@NotNull K key, int hash) {
myKey = key;
myHash = hash;
}
private void clear() {
myKey = null;
}
@Override
public K get() {
return myKey;
}
public boolean equals(Object o) {
return o.equals(this); // see WeakKey.equals()
}
public int hashCode() {
return myHash;
}
@NotNull
@Override
public ValueReference<K, V> getValueReference() {
throw new UnsupportedOperationException();
}
}
private static final ThreadLocal<HardKey> HARD_KEY = new ThreadLocal<HardKey>() {
@Override
protected HardKey initialValue() {
return new HardKey();
}
};
@NotNull
private HardKey<K,V> createHardKey(Object o) {
@SuppressWarnings("unchecked") K key = (K)o;
@SuppressWarnings("unchecked") HardKey<K,V> hardKey = HARD_KEY.get();
hardKey.set(key, myHashingStrategy.computeHashCode(key));
return hardKey;
}
///////////////////////////////////
@Override
public V get(@NotNull Object key) {
HardKey<K,V> hardKey = createHardKey(key);
ValueReference<K, V> valueReference = myMap.get(hardKey);
V v = deref(valueReference);
hardKey.clear();
return v;
}
@Override
public boolean containsKey(Object key) {
HardKey<K,V> hardKey = createHardKey(key);
boolean result = myMap.containsKey(hardKey);
hardKey.clear();
return result;
}
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public V remove(Object key) {
processQueues();
HardKey<K,V> hardKey = createHardKey(key);
ValueReference<K, V> valueReference = myMap.remove(hardKey);
V v = deref(valueReference);
hardKey.clear();
return v;
}
public void putAll(@NotNull Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
@Override
public V put(K key, V value) {
processQueues();
KeyReference<K, V> keyReference = createKeyReference(key, value);
ValueReference<K,V> valueReference = keyReference.getValueReference();
ValueReference<K, V> prevValReference = myMap.put(keyReference, valueReference);
return deref(prevValReference);
}
private boolean processQueues() {
boolean removed = false;
KeyReference<K,V> keyReference;
while ((keyReference = (KeyReference<K, V>)myKeyQueue.poll()) != null) {
ValueReference<K, V> valueReference = keyReference.getValueReference();
removed |= myMap.remove(keyReference, valueReference);
}
ValueReference<K,V> valueReference;
while ((valueReference = (ValueReference<K, V>)myValueQueue.poll()) != null) {
keyReference = valueReference.getKeyReference();
removed |= myMap.remove(keyReference, valueReference);
}
return removed;
}
@NotNull
@Override
public Set<K> keySet() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Collection<V> values() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(@NotNull Object key, @NotNull Object value) {
processQueues();
HardKey<K, V> hardKey = createHardKey(key);
ValueReference<K, V> valueReference = myMap.get(hardKey);
V v = deref(valueReference);
boolean result = value.equals(v) && myMap.remove(hardKey, valueReference);
hardKey.clear();
return result;
}
@Override
public V putIfAbsent(@NotNull K key, @NotNull V value) {
KeyReference<K, V> keyReference = createKeyReference(key, value);
ValueReference<K, V> valueReference = keyReference.getValueReference();
ValueReference<K, V> result = myMap.putIfAbsent(keyReference, valueReference);
return deref(result);
}
@Override
public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) {
KeyReference<K, V> oldKeyReference = createKeyReference(key, oldValue);
ValueReference<K, V> oldValueReference = oldKeyReference.getValueReference();
KeyReference<K, V> newKeyReference = createKeyReference(key, newValue);
ValueReference<K, V> newValueReference = newKeyReference.getValueReference();
return myMap.replace(oldKeyReference, oldValueReference, newValueReference);
}
@Override
public V replace(@NotNull K key, @NotNull V value) {
KeyReference<K, V> keyReference = createKeyReference(key, value);
ValueReference<K, V> valueReference = keyReference.getValueReference();
ValueReference<K, V> result = myMap.replace(keyReference, valueReference);
return deref(result);
}
}