com.intellij.util.containers.ConcurrentRefHashMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of util Show documentation
Show all versions of util Show documentation
A packaging of the IntelliJ Community Edition util library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* Copyright 2000-2015 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.Nullable;
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 extends AbstractMap implements ConcurrentMap, TObjectHashingStrategy {
protected final ReferenceQueue myReferenceQueue = new ReferenceQueue();
private final ConcurrentMap, V> myMap; // hashing strategy must be canonical, we compute corresponding hash codes using our own myHashingStrategy
@NotNull
private final TObjectHashingStrategy myHashingStrategy;
interface KeyReference {
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 createKeyReference(@NotNull K key, @NotNull V value, @NotNull TObjectHashingStrategy hashingStrategy);
private static final HardKey NULL_KEY = new HardKey() {
@Override
public Object get() {
return null;
}
@Override
void setKey(Object key, int hash) {
}
};
@NotNull
private KeyReference createKeyReference(@Nullable K key, @NotNull V value) {
if (key == null) {
//noinspection unchecked
return NULL_KEY;
}
return createKeyReference(key, value, myHashingStrategy);
}
// returns true if some keys were processed
boolean processQueue() {
KeyReference wk;
boolean processed = false;
while ((wk = (KeyReference)myReferenceQueue.poll()) != null) {
V value = wk.getValue();
myMap.remove(wk, value);
processed = true;
}
return processed;
}
public ConcurrentRefHashMap(@NotNull Map t) {
this(Math.max(2 * t.size(), 11), ConcurrentHashMap.LOAD_FACTOR);
putAll(t);
}
public ConcurrentRefHashMap() {
this(ConcurrentHashMap.DEFAULT_CAPACITY);
}
public ConcurrentRefHashMap(int initialCapacity) {
this(initialCapacity, ConcurrentHashMap.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 hashingStrategy) {
this(ConcurrentHashMap.DEFAULT_CAPACITY, ConcurrentHashMap.LOAD_FACTOR, 2, hashingStrategy);
}
public ConcurrentRefHashMap(int initialCapacity,
float loadFactor,
int concurrencyLevel,
@NotNull TObjectHashingStrategy hashingStrategy) {
myHashingStrategy = hashingStrategy == THIS ? this : hashingStrategy;
myMap = ContainerUtil., V>newConcurrentMap(initialCapacity, loadFactor, concurrencyLevel, CANONICAL);
}
@Override
public int size() {
return entrySet().size();
}
@Override
public boolean isEmpty() {
return entrySet().isEmpty();
}
@Override
public boolean containsKey(@Nullable Object key) {
HardKey hardKey = createHardKey(key);
boolean result = myMap.containsKey(hardKey);
releaseHardKey(hardKey);
return result;
}
private static class HardKey implements KeyReference {
private K myKey;
private int myHash;
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();
}
@Override
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 HARD_KEY = new ThreadLocal() {
@Override
protected HardKey initialValue() {
return new HardKey();
}
};
@NotNull
private HardKey createHardKey(@Nullable Object o) {
if (o == null) {
//noinspection unchecked
return NULL_KEY;
}
@SuppressWarnings("unchecked") K key = (K)o;
@SuppressWarnings("unchecked") HardKey hardKey = HARD_KEY.get();
hardKey.setKey(key, myHashingStrategy.computeHashCode(key));
return hardKey;
}
private static void releaseHardKey(@NotNull HardKey key) {
key.setKey(null, 0);
}
@Override
public V get(@Nullable Object key) {
HardKey hardKey = createHardKey(key);
V result = myMap.get(hardKey);
releaseHardKey(hardKey);
return result;
}
@Override
public V put(@Nullable K key, @NotNull V value) {
processQueue();
KeyReference weakKey = createKeyReference(key, value);
return myMap.put(weakKey, value);
}
@Override
public V remove(@Nullable Object key) {
processQueue();
HardKey hardKey = createHardKey(key);
V result = myMap.remove(hardKey);
releaseHardKey(hardKey);
return result;
}
@Override
public void clear() {
processQueue();
myMap.clear();
}
private static class RefEntry implements Map.Entry {
private final Map.Entry ent;
private final K key; /* Strong reference to key, so that the GC
will leave it alone as long as this Entry
exists */
RefEntry(@NotNull Map.Entry ent, @Nullable 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(@NotNull 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> {
Set, V>> hashEntrySet = myMap.entrySet();
@NotNull
@Override
public Iterator> iterator() {
return new Iterator>() {
private final Iterator, V>> hashIterator = hashEntrySet.iterator();
private RefEntry next;
@Override
public boolean hasNext() {
while (hashIterator.hasNext()) {
Map.Entry, V> ent = hashIterator.next();
KeyReference wk = ent.getKey();
K k = null;
if (wk != null && (k = wk.get()) == null && wk != NULL_KEY) {
/* Weak key has been cleared by GC */
continue;
}
next = new RefEntry(ent, k);
return true;
}
return false;
}
@Override
public Map.Entry next() {
if (next == null && !hasNext()) {
throw new NoSuchElementException();
}
RefEntry 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;
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> entrySet;
@NotNull
@Override
public Set> entrySet() {
if (entrySet == null) entrySet = new EntrySet();
return entrySet;
}
@Override
public V putIfAbsent(@Nullable final K key, @NotNull V value) {
processQueue();
return myMap.putIfAbsent(createKeyReference(key, value), value);
}
@Override
public boolean remove(@Nullable final Object key, @NotNull Object value) {
processQueue();
return myMap.remove(createKeyReference((K)key, (V)value), value);
}
@Override
public boolean replace(@Nullable final K key, @NotNull final V oldValue, @NotNull final V newValue) {
processQueue();
return myMap.replace(createKeyReference(key, oldValue), oldValue, newValue);
}
@Override
public V replace(@Nullable final K key, @NotNull final V value) {
processQueue();
return myMap.replace(createKeyReference(key, value), 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();
}
}