blob: e543f9745800b60416fd35f2fd5de2d4c66ee583 [file] [log] [blame]
/*
* Copyright 2000-2013 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 com.intellij.util.EventDispatcher;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.Iterator;
public class ObjectIntCache<K> implements Iterable {
public static final int defaultSize = 8192;
public static final int minSize = 4;
protected int myTop;
protected int myBack;
protected final CacheEntry[] myCache;
protected final int[] myHashTable;
protected int myHashTableSize;
protected int myCount;
protected int myFirstFree;
protected final EventDispatcher<DeletedPairsListener> myEventDispatcher = EventDispatcher.create(DeletedPairsListener.class);
private static final int[] tableSizes =
new int[]{5, 11, 23, 47, 101, 199, 397, 797, 1597, 3191, 6397, 12799, 25589, 51199,
102397, 204793, 409579, 819157, 2295859, 4591721, 9183457, 18366923, 36733847,
73467739, 146935499, 293871013, 587742049, 1175484103};
private long myAttempts;
private long myHits;
protected static class CacheEntry {
public Object key;
public int value;
public int prev;
public int next;
public int hash_next;
}
public ObjectIntCache() {
this(defaultSize);
}
public ObjectIntCache(int cacheSize) {
if (cacheSize < minSize) {
cacheSize = minSize;
}
myTop = myBack = 0;
myCache = new CacheEntry[cacheSize + 1];
for (int i = 0; i < myCache.length; ++i) {
myCache[i] = new CacheEntry();
}
myHashTableSize = cacheSize;
int i = 0;
for (; myHashTableSize > tableSizes[i];) ++i;
myHashTableSize = tableSizes[i];
myHashTable = new int[myHashTableSize];
myAttempts = 0;
myHits = 0;
myCount = myFirstFree = 0;
}
// Some AbstractMap functions started
public boolean isEmpty() {
return count() == 0;
}
public boolean containsKey(K key) {
return isCached(key);
}
public int get(K key) {
return tryKey(key);
}
public int put(K key, int value) {
int oldValue = tryKey(key);
if (oldValue != Integer.MIN_VALUE) {
remove(key);
}
cacheObject(key, value);
return oldValue;
}
public void remove(K key) {
int index = searchForCacheEntry(key);
if (index != 0) {
removeEntry(index);
removeEntryFromHashTable(index);
myCache[index].hash_next = myFirstFree;
myFirstFree = index;
fireListenersAboutDeletion(index);
myCache[index].key = null;
}
}
public void removeAll() {
final ArrayList<K> keys = new ArrayList<K>(count());
for (int current = myTop; current > 0;) {
if (myCache[current].key != null) {
keys.add((K)myCache[current].key);
}
current = myCache[current].next;
}
for (K key : keys) {
remove(key);
}
}
// Some AbstractMap functions finished
public final void cacheObject(K key, int x) {
int index = myFirstFree;
if (myCount < myCache.length - 1) {
if (index == 0) {
index = myCount;
++index;
}
else {
myFirstFree = myCache[index].hash_next;
}
if (myCount == 0) {
myBack = index;
}
}
else {
index = myBack;
removeEntryFromHashTable(index);
fireListenersAboutDeletion(index);
myCache[myBack = myCache[index].prev].next = 0;
}
myCache[index].key = key;
myCache[index].value = x;
addEntry2HashTable(index);
add2Top(index);
}
public final int tryKey(K key) {
++myAttempts;
int index = searchForCacheEntry(key);
if (index == 0) {
return Integer.MIN_VALUE;
}
++myHits;
if (index != myTop) {
removeEntry(index);
add2Top(index);
}
return myCache[index].value;
}
public final boolean isCached(K key) {
return searchForCacheEntry(key) != 0;
}
public int count() {
return myCount;
}
public int size() {
return myCache.length - 1;
}
public double hitRate() {
return (myAttempts > 0) ? ((double)myHits / (double)myAttempts) : 0;
}
private void add2Top(int index) {
myCache[index].next = myTop;
myCache[index].prev = 0;
myCache[myTop].prev = index;
myTop = index;
}
private void removeEntry(int index) {
if (index == myBack) {
myBack = myCache[index].prev;
}
else {
myCache[myCache[index].next].prev = myCache[index].prev;
}
if (index == myTop) {
myTop = myCache[index].next;
}
else {
myCache[myCache[index].prev].next = myCache[index].next;
}
}
private void addEntry2HashTable(int index) {
int hash_index = (myCache[index].key.hashCode() & 0x7fffffff) % myHashTableSize;
myCache[index].hash_next = myHashTable[hash_index];
myHashTable[hash_index] = index;
++myCount;
}
private void removeEntryFromHashTable(int index) {
int hash_index = (myCache[index].key.hashCode() & 0x7fffffff) % myHashTableSize;
int current = myHashTable[hash_index];
int previous = 0;
int next;
while (current != 0) {
next = myCache[current].hash_next;
if (current == index) {
if (previous != 0) {
myCache[previous].hash_next = next;
}
else {
myHashTable[hash_index] = next;
}
--myCount;
break;
}
previous = current;
current = next;
}
}
private int searchForCacheEntry(K key) {
int index = (key.hashCode() & 0x7fffffff) % myHashTableSize;
int current = myHashTable[index];
myCache[0].key = key;
while (!key.equals(myCache[current].key)) {
current = myCache[current].hash_next;
}
return current;
}
// start of Iterable implementation
@Override
public Iterator iterator() {
return new ObjectCacheIterator<K>(this);
}
protected class ObjectCacheIterator<K> implements Iterator {
private final ObjectIntCache<K> myCache;
private int myCurrentEntry;
public ObjectCacheIterator(ObjectIntCache<K> cache) {
myCache = cache;
myCurrentEntry = 0;
cache.myCache[0].next = cache.myTop;
}
@Override
public boolean hasNext() {
return (myCurrentEntry = myCache.myCache[myCurrentEntry].next) != 0;
}
@Override
public Object next() {
return myCache.myCache[myCurrentEntry].value;
}
@Override
public void remove() {
myCache.remove((K)myCache.myCache[myCurrentEntry].key);
}
}
// end of Iterable implementation
// start of listening features
public interface DeletedPairsListener extends EventListener {
void objectRemoved(Object key, Object value);
}
public void addDeletedPairsListener(DeletedPairsListener listener) {
myEventDispatcher.addListener(listener);
}
public void removeDeletedPairsListener(DeletedPairsListener listener) {
myEventDispatcher.addListener(listener);
}
private void fireListenersAboutDeletion(int index) {
final CacheEntry cacheEntry = myCache[index];
myEventDispatcher.getMulticaster().objectRemoved(cacheEntry.key, cacheEntry.value);
}
// end of listening features
}