mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Ahh horrible bugs in processing soft/weak reference queue in concurrent collections
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 java.lang.ref.ReferenceQueue;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Fully copied from java.util.WeakHashMap except "get" method optimization.
|
||||
*/
|
||||
public abstract class ConcurrentRefHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V> {
|
||||
protected interface Key<K, V>{
|
||||
K get();
|
||||
V getValue();
|
||||
// MUST work even with gced references for the code in processQueue to work
|
||||
boolean equals(Object o);
|
||||
int hashCode();
|
||||
}
|
||||
|
||||
protected abstract Key<K, V> createKey(@NotNull K key, V value);
|
||||
|
||||
|
||||
private static class HardKey<K,V> implements Key<K,V> {
|
||||
private K myKey;
|
||||
private int myHash;
|
||||
private final V value;
|
||||
|
||||
public HardKey(K key, V value) {
|
||||
this.value = value;
|
||||
setKey(key);
|
||||
}
|
||||
|
||||
private void setKey(K key) {
|
||||
myKey = key;
|
||||
myHash = key != null ? key.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K get() {
|
||||
return myKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
if (t == u) return true;
|
||||
return t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return myHash;
|
||||
}
|
||||
}
|
||||
|
||||
private final ConcurrentMap<Key<K, V>, V> myMap;
|
||||
private static final Key NULL_KEY = new Key() {
|
||||
@Override
|
||||
public Object get() { return null; }
|
||||
@Override
|
||||
public Object getValue() { return null; }
|
||||
};
|
||||
|
||||
protected final ReferenceQueue<K> myReferenceQueue = new ReferenceQueue<K>();
|
||||
|
||||
private void processQueue() {
|
||||
Key<K,V> wk;
|
||||
while((wk = (Key)myReferenceQueue.poll()) != null){
|
||||
V value = wk.getValue();
|
||||
myMap.remove(wk, value);
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrentRefHashMap(int initialCapacity, float loadFactor) {
|
||||
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity, loadFactor, 4);
|
||||
}
|
||||
|
||||
public ConcurrentRefHashMap(int initialCapacity) {
|
||||
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity);
|
||||
}
|
||||
|
||||
public ConcurrentRefHashMap() {
|
||||
myMap = new ConcurrentHashMap<Key<K, V>, V>();
|
||||
}
|
||||
|
||||
public ConcurrentRefHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, TObjectHashingStrategy<Key<K, V>> hashingStrategy) {
|
||||
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity, loadFactor, concurrencyLevel, hashingStrategy);
|
||||
}
|
||||
|
||||
public ConcurrentRefHashMap(Map<? extends K, ? extends V> t) {
|
||||
this(Math.max(2 * t.size(), 11), 0.75f);
|
||||
putAll(t);
|
||||
}
|
||||
|
||||
public ConcurrentRefHashMap(final TObjectHashingStrategy<K> hashingStrategy) {
|
||||
myMap = new ConcurrentHashMap<Key<K, V>, V>(new TObjectHashingStrategy<Key<K, V>>() {
|
||||
@Override
|
||||
public int computeHashCode(final Key<K, V> object) {
|
||||
return hashingStrategy.computeHashCode(object.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Key<K, V> o1, final Key<K, V> o2) {
|
||||
return hashingStrategy.equals(o1.get(), o2.get());
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
else{
|
||||
HardKey<K,V> hardKey = createHardKey((K)key);
|
||||
boolean result = myMap.containsKey(hardKey);
|
||||
releaseHardKey(hardKey);
|
||||
return result;
|
||||
}
|
||||
//return myMap.containsKey(WeakKey.create(key));
|
||||
}
|
||||
|
||||
private static final ThreadLocal<HardKey> myHardKey = new ThreadLocal<HardKey>(){
|
||||
@Override
|
||||
protected HardKey initialValue() {
|
||||
return new HardKey(null, null);
|
||||
}
|
||||
};
|
||||
|
||||
private static <K,V> HardKey<K,V> createHardKey(K key) {
|
||||
HardKey hardKey = myHardKey.get();
|
||||
hardKey.setKey(key);
|
||||
return hardKey;
|
||||
}
|
||||
|
||||
private static void releaseHardKey(HardKey key) {
|
||||
key.setKey(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
//return myMap.get(WeakKey.create(key));
|
||||
// optimization:
|
||||
if (key == null){
|
||||
return myMap.get(NULL_KEY);
|
||||
}
|
||||
else{
|
||||
HardKey<K,V> hardKey = createHardKey((K)key);
|
||||
V result = myMap.get(hardKey);
|
||||
releaseHardKey(hardKey);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
processQueue();
|
||||
Key<K, V> weakKey = key == null ? NULL_KEY : createKey(key, value);
|
||||
return myMap.put(weakKey, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
processQueue();
|
||||
|
||||
// optimization:
|
||||
if (key == null){
|
||||
return myMap.remove(NULL_KEY);
|
||||
}
|
||||
else{
|
||||
HardKey hardKey = createHardKey(key);
|
||||
V result = myMap.remove(hardKey);
|
||||
releaseHardKey(hardKey);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
processQueue();
|
||||
myMap.clear();
|
||||
}
|
||||
|
||||
private static class Entry<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 */
|
||||
|
||||
Entry(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<Key<K, V>,V>> hashEntrySet = myMap.entrySet();
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K,V>> iterator() {
|
||||
return new Iterator<Map.Entry<K,V>>() {
|
||||
Iterator<Map.Entry<Key<K, V>,V>> hashIterator = hashEntrySet.iterator();
|
||||
Entry<K,V> next = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while(hashIterator.hasNext()){
|
||||
Map.Entry<Key<K, V>, V> ent = hashIterator.next();
|
||||
Key<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 Entry<K,V>(ent, k);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<K, V> next() {
|
||||
if (next == null && !hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Entry<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 e = (Map.Entry)o;
|
||||
Object ev = e.getValue();
|
||||
|
||||
HardKey key = createHardKey(o);
|
||||
|
||||
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;
|
||||
Key wk = (Key)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;
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<K,V>> entrySet() {
|
||||
if (entrySet == null) entrySet = new EntrySet();
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(@NotNull final K key, final V value) {
|
||||
processQueue();
|
||||
return myMap.putIfAbsent(createKey(key, value), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull final Object key, final Object value) {
|
||||
processQueue();
|
||||
return myMap.remove(createKey((K)key, (V)value), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
|
||||
processQueue();
|
||||
return myMap.replace(createKey(key, oldValue), oldValue,newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(@NotNull final K key, @NotNull final V value) {
|
||||
processQueue();
|
||||
return myMap.replace(createKey(key, value), value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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 org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public abstract class ConcurrentRefValueHashMap<K,V> implements ConcurrentMap<K,V> {
|
||||
private final ConcurrentHashMap<K,MyReference<K, V>> myMap;
|
||||
protected final ReferenceQueue<V> myQueue = new ReferenceQueue<V>();
|
||||
|
||||
public ConcurrentRefValueHashMap(final Map<K, V> map) {
|
||||
this();
|
||||
putAll(map);
|
||||
}
|
||||
|
||||
public ConcurrentRefValueHashMap() {
|
||||
myMap = new ConcurrentHashMap<K, MyReference<K, V>>();
|
||||
}
|
||||
public ConcurrentRefValueHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
myMap = new ConcurrentHashMap<K, MyReference<K, V>>(initialCapacity, loadFactor, concurrencyLevel);
|
||||
}
|
||||
|
||||
protected interface MyReference<K, V> {
|
||||
K getKey();
|
||||
V get();
|
||||
}
|
||||
|
||||
private void processQueue() {
|
||||
while(true){
|
||||
MyReference<K, V> ref = (MyReference<K, V>)myQueue.poll();
|
||||
if (ref == null) break;
|
||||
myMap.remove(ref.getKey(), ref);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(@NotNull Object key) {
|
||||
MyReference<K, V> ref = myMap.get(key);
|
||||
if (ref == null) return null;
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(@NotNull K key, @NotNull V value) {
|
||||
processQueue();
|
||||
MyReference<K, V> oldRef = myMap.put(key, createRef(key, value));
|
||||
return oldRef != null ? oldRef.get() : null;
|
||||
}
|
||||
|
||||
protected abstract MyReference<K, V> createRef(K key, V value);
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(@NotNull K key, V value) {
|
||||
MyReference<K, V> newRef = createRef(key, value);
|
||||
while (true) {
|
||||
processQueue();
|
||||
MyReference<K, V> oldRef = myMap.putIfAbsent(key, newRef);
|
||||
if (oldRef == null) return null;
|
||||
final V oldVal = oldRef.get();
|
||||
if (oldVal == null) {
|
||||
if (myMap.replace(key, oldRef, newRef)) return null;
|
||||
}
|
||||
else {
|
||||
return oldVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull final Object key, final Object value) {
|
||||
processQueue();
|
||||
return myMap.remove(key, createRef((K)key, (V)value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
|
||||
processQueue();
|
||||
return myMap.replace(key, createRef(key, oldValue), createRef(key, newValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(@NotNull final K key, @NotNull final V value) {
|
||||
processQueue();
|
||||
MyReference<K, V> ref = myMap.replace(key, createRef(key, value));
|
||||
return ref == null ? null : ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
processQueue();
|
||||
MyReference<K, V> ref = myMap.remove(key);
|
||||
return ref != null ? ref.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> t) {
|
||||
processQueue();
|
||||
for (K k : t.keySet()) {
|
||||
V v = t.get(k);
|
||||
if (v != null) {
|
||||
put(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
myMap.clear();
|
||||
processQueue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myMap.size(); //?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return myMap.isEmpty(); //?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
throw new RuntimeException("method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return myMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
List<V> result = new ArrayList<V>();
|
||||
final Collection<MyReference<K, V>> refs = myMap.values();
|
||||
for (MyReference<K, V> ref : refs) {
|
||||
final V value = ref.get();
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
final Set<K> keys = keySet();
|
||||
Set<Entry<K, V>> entries = new HashSet<Entry<K, V>>();
|
||||
|
||||
for (final K key : keys) {
|
||||
final V value = get(key);
|
||||
if (value != null) {
|
||||
entries.add(new Entry<K, V>() {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException("setValue is not implemented");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@NonNls String s = "map size:" + size() + " [";
|
||||
for (K k : myMap.keySet()) {
|
||||
Object v = get(k);
|
||||
s += "'"+k + "': '" +v+"', ";
|
||||
}
|
||||
s += "] ";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
+20
-7
@@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
abstract class ConcurrentRefValueIntObjectHashMap<V> implements ConcurrentIntObjectMap<V> {
|
||||
private final StripedLockIntObjectConcurrentHashMap<IntReference<V>> myMap = new StripedLockIntObjectConcurrentHashMap<IntReference<V>>();
|
||||
@@ -34,14 +35,15 @@ abstract class ConcurrentRefValueIntObjectHashMap<V> implements ConcurrentIntObj
|
||||
V get();
|
||||
}
|
||||
|
||||
void processQueue() {
|
||||
while(true){
|
||||
IntReference ref = (IntReference)myQueue.poll();
|
||||
private void processQueue() {
|
||||
while (true) {
|
||||
@SuppressWarnings("unchecked")
|
||||
IntReference<V> ref = (IntReference)myQueue.poll();
|
||||
if (ref == null) {
|
||||
return;
|
||||
}
|
||||
int key = ref.getKey();
|
||||
myMap.remove(key);
|
||||
myMap.remove(key, ref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +51,19 @@ abstract class ConcurrentRefValueIntObjectHashMap<V> implements ConcurrentIntObj
|
||||
@Override
|
||||
public V cacheOrGet(int key, @NotNull V value) {
|
||||
processQueue();
|
||||
IntReference<V> ref = myMap.putIfAbsent(key, createReference(key, value, myQueue));
|
||||
V v = ref == null ? null : ref.get();
|
||||
return v == null ? value : v;
|
||||
IntReference<V> newRef = createReference(key, value, myQueue);
|
||||
while (true) {
|
||||
IntReference<V> ref = myMap.putIfAbsent(key, newRef);
|
||||
if (ref == null) return value; // there were no previous value
|
||||
V old = ref.get();
|
||||
if (old != null) return old;
|
||||
|
||||
// old value has been gced; need to overwrite
|
||||
boolean replaced = myMap.replace(key, ref, newRef);
|
||||
if (replaced) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,6 +125,7 @@ abstract class ConcurrentRefValueIntObjectHashMap<V> implements ConcurrentIntObj
|
||||
|
||||
@Override
|
||||
public StripedLockIntObjectConcurrentHashMap.IntEntry<V> next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
StripedLockIntObjectConcurrentHashMap.IntEntry<V> result = next;
|
||||
next = nextAliveEntry();
|
||||
return result;
|
||||
|
||||
@@ -27,356 +27,67 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Fully copied from java.util.SoftHashMap except "get" method optimization.
|
||||
*/
|
||||
public final class ConcurrentSoftHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V> {
|
||||
private interface Key<K>{
|
||||
K get();
|
||||
}
|
||||
|
||||
private static class SoftKey<K> extends SoftReference<K> implements Key<K> {
|
||||
public final class ConcurrentSoftHashMap<K,V> extends ConcurrentRefHashMap<K,V> {
|
||||
private static class SoftKey<K, V> extends SoftReference<K> implements ConcurrentRefHashMap.Key<K, V> {
|
||||
private final int myHash; /* Hashcode of key, stored here since the key may be tossed by the GC */
|
||||
private final V value;
|
||||
|
||||
private SoftKey(K k) {
|
||||
super(k);
|
||||
myHash = k.hashCode();
|
||||
}
|
||||
|
||||
public static<K> SoftKey<K> create(K k) {
|
||||
return k == null ? null : new SoftKey<K>(k);
|
||||
}
|
||||
|
||||
private SoftKey(K k, ReferenceQueue<? super K> q) {
|
||||
private SoftKey(@NotNull K k, V v, ReferenceQueue<K> q) {
|
||||
super(k, q);
|
||||
value = v;
|
||||
myHash = k.hashCode();
|
||||
}
|
||||
|
||||
private static <K> SoftKey<K> create(K k, ReferenceQueue<? super K> q) {
|
||||
return k == null ? null : new SoftKey<K>(k, q);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
return t == u || t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return myHash;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HardKey<K> implements Key<K> {
|
||||
private final K myObject;
|
||||
private final int myHash;
|
||||
|
||||
public HardKey(K object) {
|
||||
myObject = object;
|
||||
myHash = object.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public K get() {
|
||||
return myObject;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
return t == u || t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return myHash;
|
||||
}
|
||||
}
|
||||
|
||||
private final ConcurrentMap<Key<K>, V> myMap;
|
||||
private static final Key<?> NULL_KEY = new HardKey<Object>("");
|
||||
|
||||
private final ReferenceQueue<K> myReferenceQueue = new ReferenceQueue<K>();
|
||||
|
||||
private void processQueue() {
|
||||
SoftKey wk;
|
||||
while((wk = (SoftKey)myReferenceQueue.poll()) != null){
|
||||
myMap.remove(wk);
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(int initialCapacity, float loadFactor) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(initialCapacity, loadFactor, 4);
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(int initialCapacity) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(initialCapacity);
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap() {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>();
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(@NotNull Map<? extends K, ? extends V> t) {
|
||||
this(Math.max(2 * t.size(), 11), 0.75f);
|
||||
putAll(t);
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(final TObjectHashingStrategy<K> hashingStrategy) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(new TObjectHashingStrategy<Key<K>>() {
|
||||
@Override
|
||||
public int computeHashCode(final Key<K> object) {
|
||||
return hashingStrategy.computeHashCode(object.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Key<K> o1, final Key<K> o2) {
|
||||
return hashingStrategy.equals(o1.get(), o2.get());
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
else{
|
||||
HardKey<Object> hardKey = new HardKey<Object>(key);
|
||||
return myMap.containsKey(hardKey);
|
||||
}
|
||||
//return myMap.containsKey(SoftKey.create(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
//return myMap.get(SoftKey.create(key));
|
||||
// optimization:
|
||||
if (key == null){
|
||||
return myMap.get(NULL_KEY);
|
||||
}
|
||||
else{
|
||||
HardKey<Object> hardKey = new HardKey<Object>(key);
|
||||
V result = myMap.get(hardKey);
|
||||
return (V)result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
processQueue();
|
||||
SoftKey<K> softKey = SoftKey.create(key, myReferenceQueue);
|
||||
Key<K> sk = softKey == null ? (Key<K>)NULL_KEY : softKey;
|
||||
return myMap.put(sk, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
processQueue();
|
||||
|
||||
// optimization:
|
||||
if (key == null){
|
||||
return myMap.remove(NULL_KEY);
|
||||
}
|
||||
else{
|
||||
HardKey<Object> hardKey = new HardKey<Object>(key);
|
||||
return myMap.remove(hardKey);
|
||||
}
|
||||
//return myMap.remove(SoftKey.create(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
processQueue();
|
||||
myMap.clear();
|
||||
}
|
||||
|
||||
private static class Entry<K,V> implements Map.Entry<K,V> {
|
||||
private final Map.Entry<K,V> ent;
|
||||
private final K key; /* Strong reference to key, so that the GC
|
||||
will leave it alone as long as this Entry
|
||||
exists */
|
||||
|
||||
Entry(Map.Entry<K,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);
|
||||
return value;
|
||||
}
|
||||
|
||||
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());
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ConcurrentRefHashMap.Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((ConcurrentRefHashMap.Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
if (t == u) return true;
|
||||
return t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
Object v;
|
||||
return (key == null ? 0 : key.hashCode())
|
||||
^ ((v = getValue()) == null ? 0 : v.hashCode());
|
||||
return myHash;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Internal class for entry sets */
|
||||
private class EntrySet extends AbstractSet {
|
||||
Set hashEntrySet = myMap.entrySet();
|
||||
|
||||
@Override
|
||||
public Iterator iterator() {
|
||||
|
||||
return new Iterator() {
|
||||
Iterator hashIterator = hashEntrySet.iterator();
|
||||
Entry next = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while(hashIterator.hasNext()){
|
||||
Map.Entry ent = (Map.Entry)hashIterator.next();
|
||||
SoftKey wk = (SoftKey)ent.getKey();
|
||||
Object k = null;
|
||||
if (wk != null && (k = wk.get()) == null){
|
||||
/* Soft key has been cleared by GC */
|
||||
continue;
|
||||
}
|
||||
next = new Entry(ent, k);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
if (next == null && !hasNext())
|
||||
throw new NoSuchElementException();
|
||||
Entry 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();
|
||||
|
||||
// optimization:
|
||||
HardKey<K> key = new HardKey<K>(e.getKey());
|
||||
//SoftKey key = SoftKey.create(e.getKey());
|
||||
|
||||
V hv = myMap.get(key);
|
||||
boolean toRemove = hv == null ? ev == null && myMap.containsKey(key) : hv.equals(ev);
|
||||
if (toRemove){
|
||||
myMap.remove(key);
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int h = 0;
|
||||
for (Object aHashEntrySet : hashEntrySet) {
|
||||
Map.Entry ent = (Map.Entry)aHashEntrySet;
|
||||
SoftKey wk = (SoftKey)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;
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<K,V>> entrySet() {
|
||||
if (entrySet == null) entrySet = new EntrySet();
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(@NotNull final K key, final V value) {
|
||||
processQueue();
|
||||
return myMap.putIfAbsent(SoftKey.create(key, myReferenceQueue), value);
|
||||
protected ConcurrentRefHashMap.Key<K, V> createKey(@NotNull K key, V value) {
|
||||
return new SoftKey<K, V>(key, value, myReferenceQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull final Object key, final Object value) {
|
||||
processQueue();
|
||||
return myMap.remove(SoftKey.create((K)key, myReferenceQueue), value);
|
||||
public ConcurrentSoftHashMap(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
|
||||
processQueue();
|
||||
return myMap.replace(SoftKey.create(key, myReferenceQueue), oldValue,newValue);
|
||||
public ConcurrentSoftHashMap(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(@NotNull final K key, @NotNull final V value) {
|
||||
processQueue();
|
||||
return myMap.replace(SoftKey.create(key, myReferenceQueue), value);
|
||||
public ConcurrentSoftHashMap() {
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(int initialCapacity,
|
||||
float loadFactor,
|
||||
int concurrencyLevel,
|
||||
TObjectHashingStrategy<Key<K, V>> hashingStrategy) {
|
||||
super(initialCapacity, loadFactor, concurrencyLevel, hashingStrategy);
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(Map<? extends K, ? extends V> t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
public ConcurrentSoftHashMap(TObjectHashingStrategy<K> hashingStrategy) {
|
||||
super(hashingStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,209 +16,54 @@
|
||||
|
||||
package com.intellij.util.containers;
|
||||
|
||||
import com.intellij.reference.SoftReference;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Map;
|
||||
|
||||
public final class ConcurrentSoftValueHashMap<K,V> implements ConcurrentMap<K,V> {
|
||||
private final ConcurrentMap<K,MyReference<K,V>> myMap = new ConcurrentHashMap<K, MyReference<K,V>>();
|
||||
private final ReferenceQueue<MyReference<K,V>> myQueue = new ReferenceQueue<MyReference<K,V>>();
|
||||
|
||||
private static class MyReference<K,V> extends SoftReference<V> {
|
||||
private final K key;
|
||||
private MyReference(K key, V referent, ReferenceQueue q) {
|
||||
super(referent, (ReferenceQueue<? super V>)q);
|
||||
this.key = key;
|
||||
}
|
||||
public final class ConcurrentSoftValueHashMap<K,V> extends ConcurrentRefValueHashMap<K,V> {
|
||||
public ConcurrentSoftValueHashMap(Map<K, V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public ConcurrentSoftValueHashMap() {
|
||||
super();
|
||||
}
|
||||
|
||||
private void processQueue() {
|
||||
while(true){
|
||||
MyReference<K,V> ref = (MyReference<K,V>)myQueue.poll();
|
||||
if (ref == null) {
|
||||
return;
|
||||
}
|
||||
if (myMap.get(ref.key) == ref){
|
||||
myMap.remove(ref.key);
|
||||
}
|
||||
public ConcurrentSoftValueHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
super(initialCapacity, loadFactor, concurrencyLevel);
|
||||
}
|
||||
|
||||
private static class MySoftReference<K,T> extends SoftReference<T> implements ConcurrentRefValueHashMap.MyReference<K,T> {
|
||||
private final K key;
|
||||
private MySoftReference(K key, T referent, ReferenceQueue<T> q) {
|
||||
super(referent, q);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
// 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 || getClass() != o.getClass()) return false;
|
||||
|
||||
final ConcurrentRefValueHashMap.MyReference that = (ConcurrentRefValueHashMap.MyReference)o;
|
||||
|
||||
return key.equals(that.getKey()) && Comparing.equal(get(), that.get());
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return key.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
MyReference<K,V> ref = myMap.get(key);
|
||||
if (ref == null) return null;
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
processQueue();
|
||||
MyReference<K,V> oldRef = myMap.put(key, new MyReference<K,V>(key, value, myQueue));
|
||||
return oldRef != null ? oldRef.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
myMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myMap.size(); //?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return myMap.isEmpty(); //?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
throw new RuntimeException("method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return myMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
List<V> result = new ArrayList<V>();
|
||||
final Collection<MyReference<K, V>> refs = myMap.values();
|
||||
for (MyReference<K, V> ref : refs) {
|
||||
final V value = ref.get();
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
return new AbstractSet<Entry<K, V>>() {
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
final Iterator<Entry<K,MyReference<K, V>>> refEntries = myMap.entrySet().iterator();
|
||||
return new Iterator<Entry<K, V>>() {
|
||||
Entry<K, V> next;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while (next == null) {
|
||||
if (!refEntries.hasNext()) return false;
|
||||
Entry<K, MyReference<K, V>> ref = refEntries.next();
|
||||
final K k = ref.getKey();
|
||||
V v = ref.getValue().get();
|
||||
next = v == null ? null : new AbstractMap.SimpleEntry<K, V>(k,v){
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
V old = super.setValue(value);
|
||||
put(k, value);
|
||||
return old;
|
||||
}
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
Entry<K, V> r = next;
|
||||
next = null;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (next == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
ConcurrentSoftValueHashMap.this.remove(next.getKey());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return ConcurrentSoftValueHashMap.this.remove(((Entry)o).getKey()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
ConcurrentSoftValueHashMap.this.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(@NotNull final K key, final V value) {
|
||||
while (true) {
|
||||
processQueue();
|
||||
MyReference<K, V> newRef = new MyReference<K, V>(key, value, myQueue);
|
||||
MyReference<K,V> oldRef = myMap.putIfAbsent(key, newRef);
|
||||
if (oldRef == null) return null;
|
||||
final V oldVal = oldRef.get();
|
||||
if (oldVal == null) {
|
||||
if (myMap.replace(key, oldRef, newRef)) return null;
|
||||
}
|
||||
else {
|
||||
return oldVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull final Object key, final Object value) {
|
||||
processQueue();
|
||||
return myMap.remove(key, new MyReference<K,V>((K)key, (V)value, myQueue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
|
||||
processQueue();
|
||||
return myMap.replace(key, new MyReference<K,V>(key, oldValue, myQueue), new MyReference<K,V>(key, newValue, myQueue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(@NotNull final K key, @NotNull final V value) {
|
||||
processQueue();
|
||||
MyReference<K, V> ref = myMap.replace(key, new MyReference<K, V>(key, value, myQueue));
|
||||
return ref == null ? null : ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
processQueue();
|
||||
MyReference<K,V> ref = myMap.remove(key);
|
||||
return ref != null ? ref.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> t) {
|
||||
for (Entry<? extends K, ? extends V> entry : t.entrySet()) {
|
||||
V v = entry.getValue();
|
||||
if (v != null) {
|
||||
put(entry.getKey(), v);
|
||||
}
|
||||
}
|
||||
protected ConcurrentRefValueHashMap.MyReference<K, V> createRef(K key, V value) {
|
||||
return new MySoftReference<K,V>(key, value, myQueue);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -17,6 +17,7 @@
|
||||
package com.intellij.util.containers;
|
||||
|
||||
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
@@ -41,7 +42,11 @@ public class ConcurrentSoftValueIntObjectHashMap<V> extends ConcurrentRefValueIn
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
V v = get();
|
||||
return obj instanceof MyRef && ((MyRef)obj).hash == hash && v != null && v.equals(((MyRef)obj).get());
|
||||
if (!(obj instanceof MyRef)) {
|
||||
return false;
|
||||
}
|
||||
MyRef other = (MyRef)obj;
|
||||
return other.hash == hash && key == other.getKey() && Comparing.equal(v, other.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,385 +27,71 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Fully copied from java.util.WeakHashMap except "get" method optimization.
|
||||
*/
|
||||
public final class ConcurrentWeakHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V> {
|
||||
private interface Key<K>{
|
||||
K get();
|
||||
}
|
||||
|
||||
private static class WeakKey<K> extends WeakReference<K> implements Key<K> {
|
||||
public final class ConcurrentWeakHashMap<K,V> extends ConcurrentRefHashMap<K,V> {
|
||||
private static class WeakKey<K, V> extends WeakReference<K> implements Key<K, V> {
|
||||
private final int myHash; /* Hashcode of key, stored here since the key may be tossed by the GC */
|
||||
private final V value;
|
||||
|
||||
private WeakKey(K k) {
|
||||
super(k);
|
||||
myHash = k.hashCode();
|
||||
}
|
||||
|
||||
public static <K> WeakKey<K> create(K k) {
|
||||
return k == null ? null : new WeakKey<K>(k);
|
||||
}
|
||||
|
||||
private WeakKey(K k, ReferenceQueue<K> q) {
|
||||
private WeakKey(@NotNull K k, V v, ReferenceQueue<K> q) {
|
||||
super(k, q);
|
||||
value = v;
|
||||
myHash = k.hashCode();
|
||||
}
|
||||
|
||||
private static <K> WeakKey<K> create(K k, ReferenceQueue<K> q) {
|
||||
return k == null ? null : new WeakKey<K>(k, q);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
if (t == u) return true;
|
||||
return t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return myHash;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HardKey implements Key {
|
||||
private Object myObject;
|
||||
private int myHash;
|
||||
|
||||
public HardKey(Object object) {
|
||||
setObject(object);
|
||||
}
|
||||
|
||||
private void setObject(final Object object) {
|
||||
myObject = object;
|
||||
myHash = object != null ? object.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() {
|
||||
return myObject;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
if (t == u) return true;
|
||||
return t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return myHash;
|
||||
}
|
||||
}
|
||||
|
||||
private final ConcurrentMap<Key<K>, V> myMap;
|
||||
private static final Key NULL_KEY = new Key() {
|
||||
@Override
|
||||
public Object get() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private final ReferenceQueue<K> myReferenceQueue = new ReferenceQueue<K>();
|
||||
|
||||
private void processQueue() {
|
||||
WeakKey wk;
|
||||
while((wk = (WeakKey)myReferenceQueue.poll()) != null){
|
||||
myMap.remove(wk);
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(int initialCapacity, float loadFactor) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(initialCapacity, loadFactor, 4);
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(int initialCapacity) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(initialCapacity);
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap() {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>();
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, TObjectHashingStrategy<Key<K>> hashingStrategy) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(initialCapacity, loadFactor, concurrencyLevel, hashingStrategy);
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(Map<? extends K, ? extends V> t) {
|
||||
this(Math.max(2 * t.size(), 11), 0.75f);
|
||||
putAll(t);
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(final TObjectHashingStrategy<K> hashingStrategy) {
|
||||
myMap = new ConcurrentHashMap<Key<K>, V>(new TObjectHashingStrategy<Key<K>>() {
|
||||
@Override
|
||||
public int computeHashCode(final Key<K> object) {
|
||||
return hashingStrategy.computeHashCode(object.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Key<K> o1, final Key<K> o2) {
|
||||
return hashingStrategy.equals(o1.get(), o2.get());
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
else{
|
||||
HardKey hardKey = createHardKey(key);
|
||||
boolean result = myMap.containsKey(hardKey);
|
||||
releaseHardKey(hardKey);
|
||||
return result;
|
||||
}
|
||||
//return myMap.containsKey(WeakKey.create(key));
|
||||
}
|
||||
|
||||
private static final ThreadLocal<HardKey> myHardKey = new ThreadLocal<HardKey>(){
|
||||
@Override
|
||||
protected HardKey initialValue() {
|
||||
return new HardKey(null);
|
||||
}
|
||||
};
|
||||
|
||||
private static HardKey createHardKey(final Object key) {
|
||||
HardKey hardKey = myHardKey.get();
|
||||
hardKey.setObject(key);
|
||||
return hardKey;
|
||||
}
|
||||
|
||||
private static void releaseHardKey(HardKey key) {
|
||||
key.setObject(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
//return myMap.get(WeakKey.create(key));
|
||||
// optimization:
|
||||
if (key == null){
|
||||
return myMap.get(NULL_KEY);
|
||||
}
|
||||
else{
|
||||
HardKey hardKey = createHardKey(key);
|
||||
V result = myMap.get(hardKey);
|
||||
releaseHardKey(hardKey);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
processQueue();
|
||||
Key<K> weakKey = WeakKey.create(key, myReferenceQueue);
|
||||
Key<K> o = weakKey == null ? NULL_KEY : weakKey;
|
||||
return myMap.put(o, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
processQueue();
|
||||
|
||||
// optimization:
|
||||
if (key == null){
|
||||
return myMap.remove(NULL_KEY);
|
||||
}
|
||||
else{
|
||||
HardKey hardKey = createHardKey(key);
|
||||
V result = myMap.remove(hardKey);
|
||||
releaseHardKey(hardKey);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
processQueue();
|
||||
myMap.clear();
|
||||
}
|
||||
|
||||
private static class Entry<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 */
|
||||
|
||||
Entry(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);
|
||||
return value;
|
||||
}
|
||||
|
||||
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());
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Key)) return false;
|
||||
Object t = get();
|
||||
Object u = ((Key)o).get();
|
||||
if (t == null || u == null) return false;
|
||||
if (t == u) return true;
|
||||
return t.equals(u);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
Object v;
|
||||
return (key == null ? 0 : key.hashCode()) ^ ((v = getValue()) == null ? 0 : v.hashCode());
|
||||
return myHash;
|
||||
}
|
||||
}
|
||||
|
||||
/* Internal class for entry sets */
|
||||
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
|
||||
Set<Map.Entry<Key<K>,V>> hashEntrySet = myMap.entrySet();
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K,V>> iterator() {
|
||||
return new Iterator<Map.Entry<K,V>>() {
|
||||
Iterator<Map.Entry<Key<K>,V>> hashIterator = hashEntrySet.iterator();
|
||||
Entry<K,V> next = null;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while(hashIterator.hasNext()){
|
||||
Map.Entry<Key<K>, V> ent = hashIterator.next();
|
||||
WeakKey<K> wk = (WeakKey<K>)ent.getKey();
|
||||
K k = null;
|
||||
if (wk != null && (k = wk.get()) == null){
|
||||
/* Weak key has been cleared by GC */
|
||||
continue;
|
||||
}
|
||||
next = new Entry<K,V>(ent, k);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<K, V> next() {
|
||||
if (next == null && !hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Entry<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 e = (Map.Entry)o;
|
||||
Object ev = e.getValue();
|
||||
|
||||
HardKey key = createHardKey(o);
|
||||
|
||||
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;
|
||||
WeakKey wk = (WeakKey)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;
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<K,V>> entrySet() {
|
||||
if (entrySet == null) entrySet = new EntrySet();
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(@NotNull final K key, final V value) {
|
||||
processQueue();
|
||||
return myMap.putIfAbsent(WeakKey.create(key, myReferenceQueue), value);
|
||||
protected Key<K, V> createKey(@NotNull K key, V value) {
|
||||
return new WeakKey<K, V>(key, value, myReferenceQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull final Object key, final Object value) {
|
||||
processQueue();
|
||||
return myMap.remove(WeakKey.create((K)key, myReferenceQueue), value);
|
||||
public ConcurrentWeakHashMap(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
|
||||
processQueue();
|
||||
return myMap.replace(WeakKey.create(key, myReferenceQueue), oldValue,newValue);
|
||||
public ConcurrentWeakHashMap(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(@NotNull final K key, @NotNull final V value) {
|
||||
processQueue();
|
||||
return myMap.replace(WeakKey.create(key, myReferenceQueue), value);
|
||||
public ConcurrentWeakHashMap() {
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(int initialCapacity,
|
||||
float loadFactor,
|
||||
int concurrencyLevel,
|
||||
TObjectHashingStrategy<Key<K, V>> hashingStrategy) {
|
||||
super(initialCapacity, loadFactor, concurrencyLevel, hashingStrategy);
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(Map<? extends K, ? extends V> t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
public ConcurrentWeakHashMap(TObjectHashingStrategy<K> hashingStrategy) {
|
||||
super(hashingStrategy);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,219 +17,53 @@
|
||||
package com.intellij.util.containers;
|
||||
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class ConcurrentWeakValueHashMap<K,V> implements ConcurrentMap<K,V> {
|
||||
private final ConcurrentHashMap<K,MyReference<K,V>> myMap;
|
||||
private final ReferenceQueue<V> myQueue = new ReferenceQueue<V>();
|
||||
|
||||
public ConcurrentWeakValueHashMap(final Map<K, V> map) {
|
||||
this();
|
||||
putAll(map);
|
||||
public final class ConcurrentWeakValueHashMap<K,V> extends ConcurrentRefValueHashMap<K,V> {
|
||||
public ConcurrentWeakValueHashMap(Map<K, V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public ConcurrentWeakValueHashMap() {
|
||||
myMap = new ConcurrentHashMap<K, MyReference<K,V>>();
|
||||
}
|
||||
public ConcurrentWeakValueHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
myMap = new ConcurrentHashMap<K, MyReference<K,V>>(initialCapacity, loadFactor, concurrencyLevel);
|
||||
super();
|
||||
}
|
||||
|
||||
private static class MyReference<K,T> extends WeakReference<T> {
|
||||
public ConcurrentWeakValueHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
super(initialCapacity, loadFactor, concurrencyLevel);
|
||||
}
|
||||
|
||||
private static class MyWeakReference<K,T> extends WeakReference<T> implements MyReference<K,T> {
|
||||
private final K key;
|
||||
public MyReference(K key, T referent, ReferenceQueue<? super T> q) {
|
||||
private MyWeakReference(K key, T referent, ReferenceQueue<T> q) {
|
||||
super(referent, q);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public boolean equals(final Object o) {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
// 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 || getClass() != o.getClass()) return false;
|
||||
|
||||
final MyReference that = (MyReference)o;
|
||||
|
||||
return key.equals(that.key) && Comparing.equal(get(), that.get());
|
||||
return key.equals(that.getKey()) && Comparing.equal(get(), that.get());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
public final int hashCode() {
|
||||
return key.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private void processQueue() {
|
||||
while(true){
|
||||
MyReference<K,V> ref = (MyReference<K,V>)myQueue.poll();
|
||||
if (ref == null) {
|
||||
break;
|
||||
}
|
||||
if (myMap.get(ref.key) == ref){
|
||||
myMap.remove(ref.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(@NotNull Object key) {
|
||||
MyReference<K,V> ref = myMap.get(key);
|
||||
if (ref == null) return null;
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(@NotNull K key, @NotNull V value) {
|
||||
processQueue();
|
||||
MyReference<K,V> oldRef = myMap.put(key, createRef(key, value));
|
||||
return oldRef != null ? oldRef.get() : null;
|
||||
}
|
||||
|
||||
private MyReference<K, V> createRef(K key, V value) {
|
||||
return new MyReference<K,V>(key, value, myQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(@NotNull K key, V value) {
|
||||
while (true) {
|
||||
processQueue();
|
||||
MyReference<K, V> newRef = createRef(key, value);
|
||||
MyReference<K,V> oldRef = myMap.putIfAbsent(key, newRef);
|
||||
if (oldRef == null) return null;
|
||||
final V oldVal = oldRef.get();
|
||||
if (oldVal == null) {
|
||||
if (myMap.replace(key, oldRef, newRef)) return null;
|
||||
}
|
||||
else {
|
||||
return oldVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(@NotNull final Object key, final Object value) {
|
||||
processQueue();
|
||||
return myMap.remove(key, createRef((K)key, (V)value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
|
||||
processQueue();
|
||||
return myMap.replace(key, createRef(key, oldValue), createRef(key, newValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(@NotNull final K key, @NotNull final V value) {
|
||||
processQueue();
|
||||
MyReference<K, V> ref = myMap.replace(key, createRef(key, value));
|
||||
return ref == null ? null : ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
processQueue();
|
||||
MyReference<K,V> ref = myMap.remove(key);
|
||||
return ref != null ? ref.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> t) {
|
||||
processQueue();
|
||||
for (K k : t.keySet()) {
|
||||
V v = t.get(k);
|
||||
if (v != null) {
|
||||
put(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
myMap.clear();
|
||||
processQueue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myMap.size(); //?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return myMap.isEmpty(); //?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
throw new RuntimeException("method not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return myMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
List<V> result = new ArrayList<V>();
|
||||
final Collection<MyReference<K, V>> refs = myMap.values();
|
||||
for (MyReference<K, V> ref : refs) {
|
||||
final V value = ref.get();
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
final Set<K> keys = keySet();
|
||||
Set<Entry<K, V>> entries = new HashSet<Entry<K, V>>();
|
||||
|
||||
for (final K key : keys) {
|
||||
final V value = get(key);
|
||||
if (value != null) {
|
||||
entries.add(new Entry<K, V>() {
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException("setValue is not implemented");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@NonNls String s = "ConcurrentWeakValueHashMap size:" + size() + " [";
|
||||
for (K k : myMap.keySet()) {
|
||||
Object v = get(k);
|
||||
s += "'"+k + "': '" +v+"', ";
|
||||
}
|
||||
s += "] ";
|
||||
return s;
|
||||
protected MyReference<K, V> createRef(K key, V value) {
|
||||
return new MyWeakReference<K,V>(key, value, myQueue);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -17,6 +17,7 @@
|
||||
package com.intellij.util.containers;
|
||||
|
||||
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
@@ -41,7 +42,11 @@ public class ConcurrentWeakValueIntObjectHashMap<V> extends ConcurrentRefValueIn
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
V v = get();
|
||||
return obj instanceof MyRef && ((MyRef)obj).hash == hash && v != null && v.equals(((MyRef)obj).get());
|
||||
if (!(obj instanceof MyRef)) {
|
||||
return false;
|
||||
}
|
||||
MyRef other = (MyRef)obj;
|
||||
return other.hash == hash && key == other.getKey() && Comparing.equal(v, other.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user