blob: 9863f808d4b7e981f6e956c3f49f173abf560d80 [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 java.util.EventListener;
import java.util.Iterator;
/**
* @author lvo
*/
public class IntObjectCache<T> extends ObjectCacheBase implements Iterable<T> {
public static final int DEFAULT_SIZE = 8192;
public static final int MIN_SIZE = 4;
private int myTop;
private int myBack;
private CacheEntry<T>[] myCache;
private int[] myHashTable;
private int myHashTableSize;
private int myCount;
private int myFirstFree;
private DeletedPairsListener[] myListeners;
private int myAttempts;
private int myHits;
protected static class CacheEntry<T> {
public int key;
public T value;
public int prev;
public int next;
public int hash_next;
}
public IntObjectCache() {
this(DEFAULT_SIZE);
}
public IntObjectCache(int cacheSize) {
if (cacheSize < MIN_SIZE) {
cacheSize = MIN_SIZE;
}
myTop = myBack = 0;
myCache = new CacheEntry[cacheSize + 1];
for (int i = 0; i < myCache.length; ++i) {
myCache[i] = new CacheEntry<T>();
}
myHashTableSize = getAdjustedTableSize(cacheSize);
myHashTable = new int[myHashTableSize];
myAttempts = 0;
myHits = 0;
myCount = myFirstFree = 0;
}
// Some AbstractMap functions started
public boolean isEmpty() {
return count() == 0;
}
public boolean containsKey(int key) {
return isCached(key);
}
public T get(int key) {
return tryKey(key);
}
public T put(int key, T value) {
T oldValue = tryKey(key);
if (oldValue != null) {
remove(key);
}
cacheObject(key, value);
return oldValue;
}
public void remove(int key) {
int index = searchForCacheEntry(key);
if (index != 0) {
removeEntry(index);
removeEntryFromHashTable(index);
myCache[index].hash_next = myFirstFree;
myFirstFree = index;
final CacheEntry cacheEntry = myCache[index];
final int deletedKey = cacheEntry.key;
final Object deletedValue = cacheEntry.value;
myCache[index].value = null;
fireListenersAboutDeletion(deletedKey, deletedValue);
}
}
public void removeAll() {
final IntArrayList keys = new IntArrayList(count());
int current = myTop;
while (current > 0) {
if (myCache[current].value != null) {
keys.add(myCache[current].key);
}
current = myCache[current].next;
}
for (int i = 0; i < keys.size(); ++ i) {
remove(keys.get(i));
}
}
// Some AbstractMap functions finished
public final void cacheObject(int key, T x) {
int deletedKey = 0;
Object deletedValue = null;
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);
final CacheEntry cacheEntry = myCache[index];
deletedKey = cacheEntry.key;
deletedValue = cacheEntry.value;
myCache[myBack = myCache[index].prev].next = 0;
}
myCache[index].key = key;
myCache[index].value = x;
addEntry2HashTable(index);
add2Top(index);
if (deletedValue != null) {
fireListenersAboutDeletion(deletedKey, deletedValue);
}
}
public final T tryKey(int key) {
++myAttempts;
final int index = searchForCacheEntry(key);
if (index == 0) {
return null;
}
++myHits;
final CacheEntry<T> cacheEntry = myCache[index];
final int top = myTop;
if (index != top) {
final int prev = cacheEntry.prev;
final int next = cacheEntry.next;
if (index == myBack) {
myBack = prev;
}
else {
myCache[next].prev = prev;
}
myCache[prev].next = next;
cacheEntry.next = top;
cacheEntry.prev = 0;
myCache[top].prev = index;
myTop = index;
}
return cacheEntry.value;
}
public final boolean isCached(int key) {
return searchForCacheEntry(key) != 0;
}
public int count() {
return myCount;
}
public int size() {
return myCache.length - 1;
}
public void resize(int newSize) {
final IntObjectCache<T> newCache = new IntObjectCache<T>(newSize);
final CacheEntry<T>[] cache = myCache;
int back = myBack;
while (back != 0) {
final CacheEntry<T> cacheEntry = cache[back];
newCache.cacheObject(cacheEntry.key, cacheEntry.value);
back = cacheEntry.prev;
}
myTop = newCache.myTop;
myBack = newCache.myBack;
myCache = newCache.myCache;
myHashTable = newCache.myHashTable;
myHashTableSize = newCache.myHashTableSize;
myCount = newCache.myCount;
myFirstFree = newCache.myFirstFree;
}
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 & 0x7fffffff) % myHashTableSize;
myCache[index].hash_next = myHashTable[hash_index];
myHashTable[hash_index] = index;
++myCount;
}
private void removeEntryFromHashTable(int index) {
final int hash_index = (myCache[index].key & 0x7fffffff) % myHashTableSize;
int current = myHashTable[hash_index];
int previous = 0;
while (current != 0) {
int 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(int key) {
myCache[0].key = key;
int current = myHashTable[((key & 0x7fffffff) % myHashTableSize)];
while (true) {
final CacheEntry<T> cacheEntry = myCache[current];
if (key == cacheEntry.key) {
break;
}
current = cacheEntry.hash_next;
}
return current;
}
// start of Iterable implementation
@Override
public Iterator<T> iterator() {
return new IntObjectCacheIterator(this);
}
protected class IntObjectCacheIterator implements Iterator<T> {
private int myCurrentEntry;
public IntObjectCacheIterator(IntObjectCache cache) {
myCurrentEntry = 0;
cache.myCache[0].next = cache.myTop;
}
@Override
public boolean hasNext() {
return (myCurrentEntry = myCache[myCurrentEntry].next) != 0;
}
@Override
public T next() {
return myCache[myCurrentEntry].value;
}
@Override
public void remove() {
removeEntry(myCache[myCurrentEntry].key);
}
}
// end of Iterable implementation
// start of listening features
public interface DeletedPairsListener extends EventListener {
void objectRemoved(int key, Object value);
}
public void addDeletedPairsListener(DeletedPairsListener listener) {
if (myListeners == null) {
myListeners = new DeletedPairsListener[1];
}
else {
DeletedPairsListener[] newListeners = new DeletedPairsListener[myListeners.length + 1];
System.arraycopy(myListeners, 0, newListeners, 0, myListeners.length);
myListeners = newListeners;
}
myListeners[myListeners.length - 1] = listener;
}
public void removeDeletedPairsListener(DeletedPairsListener listener) {
if (myListeners != null) {
if (myListeners.length == 1) {
myListeners = null;
}
else {
DeletedPairsListener[] newListeners = new DeletedPairsListener[myListeners.length - 1];
int i = 0;
for (DeletedPairsListener myListener : myListeners) {
if (myListener != listener) {
newListeners[i++] = myListener;
}
}
myListeners = newListeners;
}
}
}
private void fireListenersAboutDeletion(final int key, final Object value) {
if (myListeners != null) {
for (DeletedPairsListener myListener : myListeners) {
myListener.objectRemoved(key, value);
}
}
}
// end of listening features
}