/* * 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 }